@Override
  protected void handleMessageInternal(Message<?> message) throws Exception {
    Object correlationKey = correlationStrategy.getCorrelationKey(message);
    Assert.state(
        correlationKey != null,
        "Null correlation not allowed.  Maybe the CorrelationStrategy is failing?");

    if (logger.isDebugEnabled()) {
      logger.debug("Handling message with correlationKey [" + correlationKey + "]: " + message);
    }

    UUID groupIdUuid = UUIDConverter.getUUID(correlationKey);
    Lock lock = this.lockRegistry.obtain(groupIdUuid.toString());

    lock.lockInterruptibly();
    try {
      ScheduledFuture<?> scheduledFuture = this.expireGroupScheduledFutures.remove(groupIdUuid);
      if (scheduledFuture != null) {
        boolean canceled = scheduledFuture.cancel(true);
        if (canceled && logger.isDebugEnabled()) {
          logger.debug(
              "Cancel 'forceComplete' scheduling for MessageGroup with Correlation Key [ "
                  + correlationKey
                  + "].");
        }
      }
      MessageGroup messageGroup = messageStore.getMessageGroup(correlationKey);
      if (this.sequenceAware) {
        messageGroup = new SequenceAwareMessageGroup(messageGroup);
      }

      if (!messageGroup.isComplete() && messageGroup.canAdd(message)) {
        if (logger.isTraceEnabled()) {
          logger.trace("Adding message to group [ " + messageGroup + "]");
        }
        messageGroup = this.store(correlationKey, message);

        if (releaseStrategy.canRelease(messageGroup)) {
          Collection<Message<?>> completedMessages = null;
          try {
            completedMessages = this.completeGroup(message, correlationKey, messageGroup);
          } finally {
            // Always clean up even if there was an exception
            // processing messages
            this.afterRelease(messageGroup, completedMessages);
          }
        } else {
          scheduleGroupToForceComplete(messageGroup);
        }
      } else {
        discardMessage(message);
      }
    } finally {
      lock.unlock();
    }
  }
  protected void forceComplete(MessageGroup group) {

    Object correlationKey = group.getGroupId();
    // UUIDConverter is no-op if already converted
    Lock lock = this.lockRegistry.obtain(UUIDConverter.getUUID(correlationKey).toString());
    boolean removeGroup = true;
    try {
      lock.lockInterruptibly();
      try {
        ScheduledFuture<?> scheduledFuture =
            this.expireGroupScheduledFutures.remove(UUIDConverter.getUUID(correlationKey));
        if (scheduledFuture != null) {
          boolean canceled = scheduledFuture.cancel(false);
          if (canceled && logger.isDebugEnabled()) {
            logger.debug("Cancel 'forceComplete' scheduling for MessageGroup [ " + group + "].");
          }
        }
        MessageGroup groupNow = group;
        /*
         * If the group argument is not already complete,
         * re-fetch it because it might have changed while we were waiting on
         * its lock. If the last modified timestamp changed, defer the completion
         * because the selection condition may have changed such that the group
         * would no longer be eligible. If the timestamp changed, it's a completely new
         * group and should not be reaped on this cycle.
         *
         * If the group argument is already complete, do not re-fetch.
         * Note: not all message stores provide a direct reference to its internal
         * group so the initial 'isComplete()` will only return true for those stores if
         * the group was already complete at the time of its selection as a candidate.
         *
         * If the group is marked complete, only consider it
         * for reaping if it's empty (and both timestamps are unaltered).
         */
        if (!group.isComplete()) {
          groupNow = this.messageStore.getMessageGroup(correlationKey);
        }
        long lastModifiedNow = groupNow.getLastModified();
        int groupSize = groupNow.size();
        if ((!groupNow.isComplete() || groupSize == 0)
            && group.getLastModified() == lastModifiedNow
            && group.getTimestamp() == groupNow.getTimestamp()) {
          if (groupSize > 0) {
            if (releaseStrategy.canRelease(groupNow)) {
              completeGroup(correlationKey, groupNow);
            } else {
              expireGroup(correlationKey, groupNow);
            }
            if (!this.expireGroupsUponTimeout) {
              afterRelease(groupNow, groupNow.getMessages(), true);
              removeGroup = false;
            }
          } else {
            /*
             * By default empty groups are removed on the same schedule as non-empty
             * groups. A longer timeout for empty groups can be enabled by
             * setting minimumTimeoutForEmptyGroups.
             */
            removeGroup =
                lastModifiedNow <= (System.currentTimeMillis() - this.minimumTimeoutForEmptyGroups);
            if (removeGroup && logger.isDebugEnabled()) {
              logger.debug("Removing empty group: " + correlationKey);
            }
          }
        } else {
          removeGroup = false;
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Group expiry candidate ("
                    + correlationKey
                    + ") has changed - it may be reconsidered for a future expiration");
          }
        }
      } catch (MessageDeliveryException e) {
        removeGroup = false;
        if (logger.isDebugEnabled()) {
          logger.debug(
              "Group expiry candidate ("
                  + correlationKey
                  + ") has been affected by MessageDeliveryException - "
                  + "it may be reconsidered for a future expiration one more time");
        }
        throw e;
      } finally {
        try {
          if (removeGroup) {
            this.remove(group);
          }
        } finally {
          lock.unlock();
        }
      }
    } catch (InterruptedException ie) {
      Thread.currentThread().interrupt();
      logger.debug("Thread was interrupted while trying to obtain lock");
    }
  }