/* * 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.eip.aggregator; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAP11Constants; import org.apache.axiom.soap.SOAP12Constants; import org.apache.axis2.AxisFault; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.ContinuationState; import org.apache.synapse.ManagedLifecycle; import org.apache.synapse.Mediator; import org.apache.synapse.MessageContext; 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.collectors.OpenEventCollector; import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector; import org.apache.synapse.aspects.flow.statistics.data.artifact.ArtifactHolder; import org.apache.synapse.aspects.flow.statistics.util.StatisticDataCollectionHelper; import org.apache.synapse.continuation.ContinuationStackManager; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.mediators.AbstractMediator; import org.apache.synapse.mediators.FlowContinuableMediator; import org.apache.synapse.mediators.Value; import org.apache.synapse.mediators.base.SequenceMediator; import org.apache.synapse.mediators.eip.SharedDataHolder; import org.apache.synapse.mediators.eip.EIPConstants; import org.apache.synapse.mediators.eip.EIPUtils; import org.apache.synapse.util.MessageHelper; import org.apache.synapse.util.xpath.SynapseXPath; import org.jaxen.JaxenException; import java.util.*; /** * Aggregate a number of messages that are determined to be for a particular group, and combine * them to form a single message which is then processed through the 'onComplete' sequence. Thus * an aggregator acts like a filter, and may look at a correlation XPath expression to select * messages for aggregation - or look at messageSequence number properties for aggregation or * let any other (i.e. non aggregatable) messages flow through * An instance of this mediator will register with a Timer to be notified after a specified timeout, * so that aggregations that never would complete could be timed out and cleared from memory and * any fault conditions handled */ public class AggregateMediator extends AbstractMediator implements ManagedLifecycle, FlowContinuableMediator { private static final Log log = LogFactory.getLog(AggregateMediator.class); /** The duration as a number of milliseconds for this aggregation to complete */ private long completionTimeoutMillis = 0; /** The maximum number of messages required to complete aggregation */ private Value minMessagesToComplete; /** The minimum number of messages required to complete aggregation */ private Value maxMessagesToComplete; /** * XPath that specifies a correlation expression that can be used to combine messages. An * example maybe //department@id="11" */ private SynapseXPath correlateExpression = null; /** * An XPath expression that may specify a selected element to be aggregated from a group of * messages to create the aggregated message * e.g. //getQuote/return would pick up and aggregate the //getQuote/return elements from a * bunch of matching messages into one aggregated message */ private SynapseXPath aggregationExpression = null; /** This holds the reference sequence name of the */ private String onCompleteSequenceRef = null; /** Inline sequence definition holder that holds the onComplete sequence */ private SequenceMediator onCompleteSequence = null; /** The active aggregates currently being processd */ private Map<String, Aggregate> activeAggregates = Collections.synchronizedMap(new HashMap<String, Aggregate>()); private String id = null; /** Property which contains the Enclosing element of the aggregated message */ private String enclosingElementPropertyName = null; /** Lock object to provide the synchronized access to the activeAggregates on checking */ private final Object lock = new Object(); /** Reference to the synapse environment */ private SynapseEnvironment synapseEnv; private boolean isAggregateComplete = false; private boolean isAggregationMessageCollected = false; public AggregateMediator() { try { aggregationExpression = new SynapseXPath("/s11:Envelope/s11:Body/child::*[position()=1] | " + "/s12:Envelope/s12:Body/child::*[position()=1]"); aggregationExpression.addNamespace("s11", SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI); aggregationExpression.addNamespace("s12", SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI); } catch (JaxenException e) { if (log.isDebugEnabled()) { handleException("Unable to set the default " + "aggregationExpression for the aggregation", e, null); } } } public void init(SynapseEnvironment se) { synapseEnv = se; if (onCompleteSequence != null) { onCompleteSequence.init(se); } else if (onCompleteSequenceRef != null) { SequenceMediator referredOnCompleteSeq = (SequenceMediator) se.getSynapseConfiguration(). getSequence(onCompleteSequenceRef); if (referredOnCompleteSeq == null || referredOnCompleteSeq.isDynamic()) { se.addUnavailableArtifactRef(onCompleteSequenceRef); } } } public void destroy() { if (onCompleteSequence != null) { onCompleteSequence.destroy(); } else if (onCompleteSequenceRef != null) { SequenceMediator referredOnCompleteSeq = (SequenceMediator) synapseEnv.getSynapseConfiguration(). getSequence(onCompleteSequenceRef); if (referredOnCompleteSeq == null || referredOnCompleteSeq.isDynamic()) { synapseEnv.removeUnavailableArtifactRef(onCompleteSequenceRef); } } } /** * Aggregate messages flowing through this mediator according to the correlation criteria * and the aggregation algorithm specified to it * * @param synCtx - MessageContext to be mediated and aggregated * @return boolean true if the complete condition for the particular aggregate is validated */ 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 : Aggregate mediator"); if (synLog.isTraceTraceEnabled()) { synLog.traceTrace("Message : " + synCtx.getEnvelope()); } } try { Aggregate aggregate = null; String correlationIdName = (id != null ? EIPConstants.AGGREGATE_CORRELATION + "." + id : EIPConstants.AGGREGATE_CORRELATION); // if a correlateExpression is provided and there is a coresponding // element in the current message prepare to correlate the messages on that Object result = null; if (correlateExpression != null) { result = correlateExpression.evaluate(synCtx); if (result instanceof List) { if (((List) result).isEmpty()) { handleException("Failed to evaluate correlate expression: " + correlateExpression.toString(), synCtx); } } } if (result != null) { while (aggregate == null) { synchronized (lock) { if (activeAggregates.containsKey(correlateExpression.toString())) { aggregate = activeAggregates.get(correlateExpression.toString()); if (aggregate != null) { if (!aggregate.getLock()) { aggregate = null; } } } else { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Creating new Aggregator - " + (completionTimeoutMillis > 0 ? "expires in : " + (completionTimeoutMillis / 1000) + "secs" : "without expiry time")); } if (isAlreadyTimedOut(synCtx)) { return false; } Double minMsg = Double.parseDouble(minMessagesToComplete.evaluateValue(synCtx)); Double maxMsg = Double.parseDouble(maxMessagesToComplete.evaluateValue(synCtx)); aggregate = new Aggregate( synCtx.getEnvironment(), correlateExpression.toString(), completionTimeoutMillis, minMsg.intValue(), maxMsg.intValue(), this); if (completionTimeoutMillis > 0) { synCtx.getConfiguration().getSynapseTimer(). schedule(aggregate, completionTimeoutMillis); } aggregate.getLock(); activeAggregates.put(correlateExpression.toString(), aggregate); } } } } else if (synCtx.getProperty(correlationIdName) != null) { // if the correlattion cannot be found using the correlateExpression then // try the default which is through the AGGREGATE_CORRELATION message property // which is the unique original message id of a split or iterate operation and // which thus can be used to uniquely group messages into aggregates Object o = synCtx.getProperty(correlationIdName); String correlation; if (o != null && o instanceof String) { correlation = (String) o; while (aggregate == null) { synchronized (lock) { if (activeAggregates.containsKey(correlation)) { aggregate = activeAggregates.get(correlation); if (aggregate != null) { if (!aggregate.getLock()) { aggregate = null; } } else { break; } } else { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Creating new Aggregator - " + (completionTimeoutMillis > 0 ? "expires in : " + (completionTimeoutMillis / 1000) + "secs" : "without expiry time")); } if (isAlreadyTimedOut(synCtx)) { return false; } Double minMsg = -1.0; if (minMessagesToComplete != null) { minMsg = Double.parseDouble(minMessagesToComplete.evaluateValue(synCtx)); } Double maxMsg = -1.0; if (maxMessagesToComplete != null) { maxMsg = Double.parseDouble(maxMessagesToComplete.evaluateValue(synCtx)); } aggregate = new Aggregate( synCtx.getEnvironment(), correlation, completionTimeoutMillis, minMsg.intValue(), maxMsg.intValue(), this); if (completionTimeoutMillis > 0) { synchronized(aggregate) { if (!aggregate.isCompleted()) { synCtx.getConfiguration().getSynapseTimer(). schedule(aggregate, completionTimeoutMillis); } } } aggregate.getLock(); activeAggregates.put(correlation, aggregate); } } } } else { synLog.traceOrDebug("Unable to find aggrgation correlation property"); return true; } } else { synLog.traceOrDebug("Unable to find aggrgation correlation XPath or property"); return true; } // if there is an aggregate continue on aggregation if (aggregate != null) { //this is a temporary fix synCtx.getEnvelope().build(); boolean collected = aggregate.addMessage(synCtx); if (synLog.isTraceOrDebugEnabled()) { if (collected) { synLog.traceOrDebug("Collected a message during aggregation"); if (synLog.isTraceTraceEnabled()) { synLog.traceTrace("Collected message : " + synCtx); } } } // check the completeness of the aggregate and if completed aggregate the messages // if not completed return false and block the message sequence till it completes if (aggregate.isComplete(synLog)) { synLog.traceOrDebug("Aggregation completed - invoking onComplete"); boolean onCompleteSeqResult = completeAggregate(aggregate); synLog.traceOrDebug("End : Aggregate mediator"); isAggregateComplete = onCompleteSeqResult; return onCompleteSeqResult; } else { aggregate.releaseLock(); } } else { // if the aggregation correlation cannot be found then continue the message on the // normal path by returning true synLog.traceOrDebug("Unable to find an aggregate for this message - skip"); return true; } } catch (JaxenException e) { handleException("Unable to execute the XPATH over the message", e, synCtx); } synLog.traceOrDebug("End : Aggregate mediator"); // When Aggregation is not completed return false to hold the flow return false; } /* * Check whether aggregate is already timed-out and we are receiving a message after the timeout interval */ private boolean isAlreadyTimedOut(MessageContext synCtx) { Object aggregateTimeoutHolderObj = synCtx.getProperty(id != null ? EIPConstants.EIP_SHARED_DATA_HOLDER + "." + id : EIPConstants.EIP_SHARED_DATA_HOLDER); if (aggregateTimeoutHolderObj != null) { SharedDataHolder sharedDataHolder = (SharedDataHolder) aggregateTimeoutHolderObj; if (sharedDataHolder.isTimeoutOccurred()) { if (log.isDebugEnabled()) { log.debug("Received a response for already timed-out Aggregate"); } return true; } } return false; } public boolean mediate(MessageContext synCtx, ContinuationState contState) { SynapseLog synLog = getLog(synCtx); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Aggregate mediator : Mediating from ContinuationState"); } boolean result; SequenceMediator onCompleteSequence = getOnCompleteSequence(); boolean isStatisticsEnabled = RuntimeStatisticCollector.isStatisticsEnabled(); if (!contState.hasChild()) { result = onCompleteSequence.mediate(synCtx, contState.getPosition() + 1); } else { FlowContinuableMediator mediator = (FlowContinuableMediator) onCompleteSequence.getChild(contState.getPosition()); result = mediator.mediate(synCtx, contState.getChildContState()); if (isStatisticsEnabled) { ((Mediator) mediator).reportCloseStatistics(synCtx, null); } } if (isStatisticsEnabled) { onCompleteSequence.reportCloseStatistics(synCtx, null); } return result; } /** * Invoked by the Aggregate objects that are timed out, to signal timeout/completion of * itself * @param aggregate the timed out Aggregate that holds collected messages and properties */ public boolean completeAggregate(Aggregate aggregate) { boolean markedCompletedNow = false; boolean wasComplete = aggregate.isCompleted(); if (wasComplete) { return false; } if (log.isDebugEnabled()) { log.debug("Aggregation completed or timed out"); } // cancel the timer synchronized(this) { if (!aggregate.isCompleted()) { aggregate.cancel(); aggregate.setCompleted(true); MessageContext lastMessage = aggregate.getLastMessage(); if (lastMessage != null) { Object aggregateTimeoutHolderObj = lastMessage.getProperty(id != null ? EIPConstants.EIP_SHARED_DATA_HOLDER + "." + id : EIPConstants.EIP_SHARED_DATA_HOLDER); if (aggregateTimeoutHolderObj != null) { SharedDataHolder sharedDataHolder = (SharedDataHolder) aggregateTimeoutHolderObj; sharedDataHolder.markTimeoutState(); } } markedCompletedNow = true; } } if (!markedCompletedNow) { return false; } MessageContext newSynCtx = getAggregatedMessage(aggregate); if (newSynCtx == null) { log.warn("An aggregation of messages timed out with no aggregated messages", null); return false; } else { isAggregationMessageCollected = true; // Get the aggregated message to the next mediator placed after the aggregate mediator // in the sequence if (newSynCtx.isContinuationEnabled()) { try { aggregate.getLastMessage().setEnvelope( MessageHelper.cloneSOAPEnvelope(newSynCtx.getEnvelope())); } catch (AxisFault axisFault) { log.warn("Error occurred while assigning aggregated message" + " back to the last received message context"); } } } aggregate.clear(); activeAggregates.remove(aggregate.getCorrelation()); if ((correlateExpression != null && !correlateExpression.toString().equals(aggregate.getCorrelation())) || correlateExpression == null) { if (onCompleteSequence != null) { ContinuationStackManager. addReliantContinuationState(newSynCtx, 0, getMediatorPosition()); boolean result = onCompleteSequence.mediate(newSynCtx); if (result) { ContinuationStackManager.removeReliantContinuationState(newSynCtx); } return result; } else if (onCompleteSequenceRef != null && newSynCtx.getSequence(onCompleteSequenceRef) != null) { ContinuationStackManager.updateSeqContinuationState(newSynCtx, getMediatorPosition()); return newSynCtx.getSequence(onCompleteSequenceRef).mediate(newSynCtx); } else { handleException("Unable to find the sequence for the mediation " + "of the aggregated message", newSynCtx); } } return false; } /** * Get the aggregated message from the specified Aggregate instance * * @param aggregate the Aggregate object that holds collected messages and properties of the * aggregation * @return the aggregated message context */ private MessageContext getAggregatedMessage(Aggregate aggregate) { MessageContext newCtx = null; for (MessageContext synCtx : aggregate.getMessages()) { if (newCtx == null) { try { newCtx = MessageHelper.cloneMessageContextForAggregateMediator(synCtx); } catch (AxisFault axisFault) { handleException("Error creating a copy of the message", axisFault, synCtx); } if (log.isDebugEnabled()) { log.debug("Generating Aggregated message from : " + newCtx.getEnvelope()); } } else { try { if (log.isDebugEnabled()) { log.debug("Merging message : " + synCtx.getEnvelope() + " using XPath : " + aggregationExpression); } EIPUtils.enrichEnvelope( newCtx.getEnvelope(), synCtx.getEnvelope(), synCtx, aggregationExpression); if (log.isDebugEnabled()) { log.debug("Merged result : " + newCtx.getEnvelope()); } } catch (JaxenException e) { handleException("Error merging aggregation results using XPath : " + aggregationExpression.toString(), e, synCtx); } catch (SynapseException e) { handleException("Error evaluating expression: " + aggregationExpression.toString() , e, synCtx); } } } // Enclose with a parent element if EnclosingElement is defined if (enclosingElementPropertyName != null) { if (log.isDebugEnabled()) { log.debug("Enclosing the aggregated message with enclosing element: " + enclosingElementPropertyName); } Object enclosingElementProperty = newCtx.getProperty(enclosingElementPropertyName); if (enclosingElementProperty != null) { if (enclosingElementProperty instanceof OMElement) { OMElement enclosingElement = ((OMElement) enclosingElementProperty).cloneOMElement(); EIPUtils.encloseWithElement(newCtx.getEnvelope(), enclosingElement); return newCtx; } else { handleException("Enclosing Element defined in the property: " + enclosingElementPropertyName + " is not an OMElement ", newCtx); } } else { handleException("Enclosing Element property: " + enclosingElementPropertyName + " not found ", newCtx); } } StatisticDataCollectionHelper.collectAggregatedParents(aggregate.getMessages(), newCtx); return newCtx; } public SynapseXPath getCorrelateExpression() { return correlateExpression; } public void setCorrelateExpression(SynapseXPath correlateExpression) { this.correlateExpression = correlateExpression; } public long getCompletionTimeoutMillis() { return completionTimeoutMillis; } public void setCompletionTimeoutMillis(long completionTimeoutMillis) { this.completionTimeoutMillis = completionTimeoutMillis; } public SynapseXPath getAggregationExpression() { return aggregationExpression; } public void setAggregationExpression(SynapseXPath aggregationExpression) { this.aggregationExpression = aggregationExpression; } public String getOnCompleteSequenceRef() { return onCompleteSequenceRef; } public void setOnCompleteSequenceRef(String onCompleteSequenceRef) { this.onCompleteSequenceRef = onCompleteSequenceRef; } public SequenceMediator getOnCompleteSequence() { return onCompleteSequence; } public void setOnCompleteSequence(SequenceMediator onCompleteSequence) { this.onCompleteSequence = onCompleteSequence; } public Map getActiveAggregates() { return activeAggregates; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Value getMinMessagesToComplete() { return minMessagesToComplete; } public void setMinMessagesToComplete(Value minMessagesToComplete) { this.minMessagesToComplete = minMessagesToComplete; } public Value getMaxMessagesToComplete() { return maxMessagesToComplete; } public void setMaxMessagesToComplete(Value maxMessagesToComplete) { this.maxMessagesToComplete = maxMessagesToComplete; } public String getEnclosingElementPropertyName() { return enclosingElementPropertyName; } public void setEnclosingElementPropertyName(String enclosingElementPropertyName) { this.enclosingElementPropertyName = enclosingElementPropertyName; } @Override public boolean isContentAltering() { return true; } @Override public Integer reportOpenStatistics(MessageContext messageContext, boolean isContentAltering) { return OpenEventCollector.reportFlowAggregateEvent(messageContext, getMediatorName(), ComponentType.MEDIATOR, getAspectConfiguration(), isContentAltering() || isContentAltering); } @Override public void setComponentStatisticsId(ArtifactHolder holder) { if (getAspectConfiguration() == null) { configure(new AspectConfiguration(getMediatorName())); } String mediatorId = StatisticIdentityGenerator.getIdForFlowContinuableMediator(getMediatorName(), ComponentType.MEDIATOR, holder); getAspectConfiguration().setUniqueId(mediatorId); if (onCompleteSequence != null) { onCompleteSequence.setComponentStatisticsId(holder); } else if (onCompleteSequenceRef != null) { String childId = StatisticIdentityGenerator.getIdReferencingComponent(onCompleteSequenceRef, ComponentType.SEQUENCE, holder); StatisticIdentityGenerator.reportingEndEvent(childId, ComponentType.SEQUENCE, holder); } StatisticIdentityGenerator.reportingFlowContinuableEndEvent(mediatorId, ComponentType.MEDIATOR, holder); } }