/**
   * 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);
        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 {
      // 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;
    StatisticsRecord destinationStatRecord = null;

    for (MessageContext synCtx : aggregate.getMessages()) {

      if (newCtx == null) {
        try {
          newCtx = MessageHelper.cloneMessageContextForAggregateMediator(synCtx);
          destinationStatRecord =
              (StatisticsRecord) newCtx.getProperty(SynapseConstants.STATISTICS_STACK);
        } 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 (destinationStatRecord != null
              && synCtx.getProperty(SynapseConstants.STATISTICS_STACK) != null) {
            // Merge the statistics logs to one single message
            // context.
            mergeStatisticsRecords(
                (StatisticsRecord) synCtx.getProperty(SynapseConstants.STATISTICS_STACK),
                destinationStatRecord);
          }

          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);
      }
    }

    if (MessageFlowTracingDataCollector.isMessageFlowTracingEnabled()) {
      List<String> newMessageFlowTrace = new ArrayList<String>();
      for (MessageContext synCtx : aggregate.getMessages()) {
        List<String> messageFlowTrace =
            (List<String>) synCtx.getProperty(MessageFlowTracerConstants.MESSAGE_FLOW);
        if (null != messageFlowTrace) {
          newMessageFlowTrace.addAll(messageFlowTrace);
        }
      }
      newCtx.setProperty(MessageFlowTracerConstants.MESSAGE_FLOW, newMessageFlowTrace);
    }

    return newCtx;
  }
  /**
   * 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().isDebugEnabled()) {
      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"));
              }

              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"));
                }

                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;
  }