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