@Test
 public void testRemoveNonExistingMessageFromTheGroup() throws Exception {
   GemfireMessageStore store = new GemfireMessageStore(this.cache);
   store.afterPropertiesSet();
   MessageGroup messageGroup = store.getMessageGroup(1);
   store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
   store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
 }
 @Test
 public void testNonExistingEmptyMessageGroup() throws Exception {
   GemfireMessageStore store = new GemfireMessageStore(this.cache);
   store.afterPropertiesSet();
   MessageGroup messageGroup = store.getMessageGroup(1);
   assertNotNull(messageGroup);
   assertTrue(messageGroup instanceof SimpleMessageGroup);
   assertEquals(0, messageGroup.size());
 }
  @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();
    }
  }
 @Test
 public void testLastReleasedSequenceNumber() throws Exception {
   GemfireMessageStore store = new GemfireMessageStore(this.cache);
   store.afterPropertiesSet();
   MessageGroup messageGroup = store.getMessageGroup(1);
   Message<?> messageToMark = new GenericMessage<String>("1");
   store.addMessageToGroup(messageGroup.getGroupId(), messageToMark);
   store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
   messageGroup = store.getMessageGroup(1);
   assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
 }
 @Test
 public void testCompleteMessageGroup() throws Exception {
   GemfireMessageStore store = new GemfireMessageStore(this.cache);
   store.afterPropertiesSet();
   MessageGroup messageGroup = store.getMessageGroup(1);
   Message<?> messageToMark = new GenericMessage<String>("1");
   store.addMessageToGroup(messageGroup.getGroupId(), messageToMark);
   store.completeGroup(messageGroup.getGroupId());
   messageGroup = store.getMessageGroup(1);
   assertTrue(messageGroup.isComplete());
 }
  @Test
  @MongoDbAvailable
  public void testNonExistingEmptyMessageGroup() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    assertNotNull(messageGroup);
    assertTrue(messageGroup instanceof SimpleMessageGroup);
    assertEquals(0, messageGroup.size());
  }
  @Test
  @MongoDbAvailable
  public void testCompleteMessageGroup() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("Hello");
    store.addMessageToGroup(messageGroup.getGroupId(), message);
    store.completeGroup(messageGroup.getGroupId());
    messageGroup = store.getMessageGroup(1);
    assertTrue(messageGroup.isComplete());
  }
  @Test
  @MongoDbAvailable
  public void testLastReleasedSequenceNumber() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("Hello");
    store.addMessageToGroup(messageGroup.getGroupId(), message);
    store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
    messageGroup = store.getMessageGroup(1);
    assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
  }
  @Test
  public void testMessageGroupWithAddedMessage() throws Exception {
    GemfireMessageStore store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();
    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("Hello");
    messageGroup = store.addMessageToGroup(1, message);
    assertEquals(1, messageGroup.size());

    // make sure the store is properly rebuild from Gemfire
    store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();

    messageGroup = store.getMessageGroup(1);
    assertEquals(1, messageGroup.size());
  }
 public Object processMessageGroup(MessageGroup group) {
   Integer product = 1;
   for (Message<?> message : group.getMessages()) {
     product *= (Integer) message.getPayload();
   }
   return product;
 }
 protected void completeGroup(Object correlationKey, MessageGroup group) {
   Message<?> first = null;
   if (group != null) {
     first = group.getOne();
   }
   completeGroup(first, correlationKey, group);
 }
  @Test
  @MongoDbAvailable
  public void testRemoveMessageFromTheGroup() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("2");
    store.addMessageToGroup(1, new GenericMessage<String>("1"));
    store.addMessageToGroup(1, message);
    messageGroup = store.addMessageToGroup(1, new GenericMessage<String>("3"));

    assertEquals(3, messageGroup.size());

    messageGroup = store.removeMessageFromGroup(1, message);
    assertEquals(2, messageGroup.size());
  }
  @Test
  @MongoDbAvailable
  public void testMessageGroupMarkingMessage() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> messageA = new GenericMessage<String>("A");
    Message<?> messageB = new GenericMessage<String>("B");
    store.addMessageToGroup(1, messageA);
    messageGroup = store.addMessageToGroup(1, messageB);
    assertEquals(0, messageGroup.getMarked().size());
    assertEquals(2, messageGroup.getUnmarked().size());

    messageGroup = store.markMessageFromGroup(1, messageA);
    assertEquals(1, messageGroup.getMarked().size());
    assertEquals(1, messageGroup.getUnmarked().size());

    // validate that the updates were propagated to Mongo as well
    store = new MongoDbMessageStore(mongoDbFactory);

    messageGroup = store.getMessageGroup(1);
    assertEquals(1, messageGroup.getMarked().size());
    assertEquals(1, messageGroup.getUnmarked().size());
  }
  @Test
  @MongoDbAvailable
  public void testMarkAllMessagesInMessageGroup() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);

    store.addMessageToGroup(1, new GenericMessage<String>("1"));
    store.addMessageToGroup(1, new GenericMessage<String>("2"));
    messageGroup = store.addMessageToGroup(1, new GenericMessage<String>("3"));

    assertEquals(3, messageGroup.getUnmarked().size());
    assertEquals(0, messageGroup.getMarked().size());

    messageGroup = store.markMessageGroup(messageGroup);

    assertEquals(0, messageGroup.getUnmarked().size());
    assertEquals(3, messageGroup.getMarked().size());

    store = new MongoDbMessageStore(mongoDbFactory);

    messageGroup = store.getMessageGroup(1);
    assertEquals(0, messageGroup.getUnmarked().size());
    assertEquals(3, messageGroup.getMarked().size());
  }
 @Test
 public void testProcessAndCheckHeaders() throws Exception {
   when(group.getMessages()).thenReturn(messages);
   processor = new ExpressionEvaluatingMessageGroupProcessor("#root");
   Object result = processor.processMessageGroup(group);
   assertTrue(result instanceof Message<?>);
   Message<?> resultMessage = (Message<?>) result;
   assertEquals("bar", resultMessage.getHeaders().get("foo"));
 }
 @Test
 public void testProcessAndSendWithSizeExpressionEvaluated() throws Exception {
   when(group.getMessages()).thenReturn(messages);
   processor = new ExpressionEvaluatingMessageGroupProcessor("#root.size()");
   Object result = processor.processMessageGroup(group);
   assertTrue(result instanceof Message<?>);
   Message<?> resultMessage = (Message<?>) result;
   assertEquals(5, resultMessage.getPayload());
 }
  @Test
  @MongoDbAvailable
  public void testMessageGroupWithAddedMessage() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> messageA = new GenericMessage<String>("A");
    Message<?> messageB = new GenericMessage<String>("B");
    store.addMessageToGroup(1, messageA);
    messageGroup = store.addMessageToGroup(1, messageB);
    assertEquals(2, messageGroup.size());
    Message<?> retrievedMessage = store.getMessage(messageA.getHeaders().getId());
    assertNotNull(retrievedMessage);
    assertEquals(retrievedMessage.getHeaders().getId(), messageA.getHeaders().getId());
    // ensure that 'message_group' header that is only used internally is not propagated
    assertNull(retrievedMessage.getHeaders().get("message_group"));
  }
  @Test
  @MongoDbAvailable
  public void testRemoveMessageGroup() throws Exception {
    MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
    MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);

    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("Hello");
    UUID id = message.getHeaders().getId();
    messageGroup = store.addMessageToGroup(1, message);
    assertEquals(1, messageGroup.size());
    message = store.getMessage(id);
    assertNotNull(message);

    store.removeMessageGroup(1);
    MessageGroup messageGroupA = store.getMessageGroup(1);
    assertEquals(0, messageGroupA.size());
    assertFalse(messageGroupA.equals(messageGroup));
  }
  @Test
  public void testMultipleInstancesOfGroupStore() throws Exception {
    GemfireMessageStore store1 = new GemfireMessageStore(this.cache);
    store1.afterPropertiesSet();

    GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
    store2.afterPropertiesSet();

    Message<?> message = new GenericMessage<String>("1");
    store1.addMessageToGroup(1, message);
    MessageGroup messageGroup = store2.addMessageToGroup(1, new GenericMessage<String>("2"));

    assertEquals(2, messageGroup.getMessages().size());

    GemfireMessageStore store3 = new GemfireMessageStore(this.cache);
    store3.afterPropertiesSet();

    messageGroup = store3.removeMessageFromGroup(1, message);

    assertEquals(1, messageGroup.getMessages().size());
  }
 @Test
 public void testProcessAndSendWithFilterAndProjectionAndMethodInvokingExpressionEvaluated()
     throws Exception {
   when(group.getMessages()).thenReturn(messages);
   processor =
       new ExpressionEvaluatingMessageGroupProcessor(
           String.format("T(%s).sum(?[payload>2].![payload])", getClass().getName()));
   Object result = processor.processMessageGroup(group);
   assertTrue(result instanceof Message<?>);
   Message<?> resultMessage = (Message<?>) result;
   assertEquals(3 + 4 + 5, resultMessage.getPayload());
 }
 protected void expireGroup(Object correlationKey, MessageGroup group) {
   if (logger.isInfoEnabled()) {
     logger.info("Expiring MessageGroup with correlationKey[" + correlationKey + "]");
   }
   if (sendPartialResultOnExpiry) {
     if (logger.isDebugEnabled()) {
       logger.debug(
           "Prematurely releasing partially complete group with key ["
               + correlationKey
               + "] to: "
               + getOutputChannel());
     }
     completeGroup(correlationKey, group);
   } else {
     if (logger.isDebugEnabled()) {
       logger.debug(
           "Discarding messages of partially complete group with key ["
               + correlationKey
               + "] to: "
               + (this.discardChannelName != null
                   ? this.discardChannelName
                   : this.discardChannel));
     }
     for (Message<?> message : group.getMessages()) {
       discardMessage(message);
     }
   }
   if (this.applicationEventPublisher != null) {
     this.applicationEventPublisher.publishEvent(
         new MessageGroupExpiredEvent(
             this,
             correlationKey,
             group.size(),
             new Date(group.getLastModified()),
             new Date(),
             !sendPartialResultOnExpiry));
   }
 }
 @Test
 public void testProcessAndSendWithFilterAndProjectionExpressionEvaluated() throws Exception {
   when(group.getMessages()).thenReturn(messages);
   processor = new ExpressionEvaluatingMessageGroupProcessor("?[payload>2].![payload]");
   Object result = processor.processMessageGroup(group);
   assertTrue(result instanceof Message<?>);
   Message<?> resultMessage = (Message<?>) result;
   assertTrue(resultMessage.getPayload() instanceof Collection<?>);
   Collection<?> list = (Collection<?>) resultMessage.getPayload();
   assertEquals(3, list.size());
   assertTrue(list.contains(3));
   assertTrue(list.contains(4));
   assertTrue(list.contains(5));
 }
  @Override
  public boolean canRelease(MessageGroup messageGroup) {

    boolean canRelease = false;

    Collection<Message<?>> messages = messageGroup.getMessages();

    if (releasePartialSequences && !messages.isEmpty()) {

      if (logger.isTraceEnabled()) {
        logger.trace("Considering partial release of group [" + messageGroup + "]");
      }
      Message<?> minMessage = Collections.min(messages, this.comparator);

      int nextSequenceNumber = new IntegrationMessageHeaderAccessor(minMessage).getSequenceNumber();
      int lastReleasedMessageSequence = messageGroup.getLastReleasedMessageSequenceNumber();

      if (nextSequenceNumber - lastReleasedMessageSequence == 1) {
        canRelease = true;
        ;
      }
    } else {
      int size = messages.size();

      if (size == 0) {
        canRelease = true;
      } else {
        int sequenceSize =
            new IntegrationMessageHeaderAccessor(messageGroup.getOne()).getSequenceSize();
        // If there is no sequence then it must be incomplete....
        if (sequenceSize == size) {
          canRelease = true;
        }
      }
    }
    return canRelease;
  }
  @Test
  public void testRemoveMessageFromTheGroup() throws Exception {
    GemfireMessageStore store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();
    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("2");

    messageGroup =
        store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
    messageGroup = store.getMessageGroup(1);
    assertEquals(1, messageGroup.size());
    Thread.sleep(
        1); // since it adds to a local region some times CREATED_DATE ends up to be the same
    // Unrealistic in a real scenario

    messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message);
    messageGroup = store.getMessageGroup(1);
    assertEquals(2, messageGroup.size());
    Thread.sleep(1);

    messageGroup =
        store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("3"));
    messageGroup = store.getMessageGroup(1);
    assertEquals(3, messageGroup.size());

    messageGroup = store.removeMessageFromGroup(messageGroup.getGroupId(), message);
    messageGroup = store.getMessageGroup(1);
    assertEquals(2, messageGroup.size());

    // make sure the store is properly rebuild from Gemfire
    store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();

    messageGroup = store.getMessageGroup(1);
    assertEquals(2, messageGroup.size());
  }
  private void scheduleGroupToForceComplete(final MessageGroup messageGroup) {
    final Long groupTimeout = this.obtainGroupTimeout(messageGroup);
    /*
     * When 'groupTimeout' is evaluated to 'null' we do nothing.
     * The 'MessageGroupStoreReaper' can be used to 'forceComplete' message groups.
     */
    if (groupTimeout != null && groupTimeout >= 0) {
      if (groupTimeout > 0) {
        ScheduledFuture<?> scheduledFuture =
            this.getTaskScheduler()
                .schedule(
                    new Runnable() {

                      @Override
                      public void run() {
                        try {
                          forceReleaseProcessor.processMessageGroup(messageGroup);
                        } catch (MessageDeliveryException e) {
                          if (logger.isDebugEnabled()) {
                            logger.debug(
                                "The MessageGroup [ "
                                    + messageGroup
                                    + "] is rescheduled by the reason: "
                                    + e.getMessage());
                          }
                          scheduleGroupToForceComplete(messageGroup);
                        }
                      }
                    },
                    new Date(System.currentTimeMillis() + groupTimeout));

        if (logger.isDebugEnabled()) {
          logger.debug("Schedule MessageGroup [ " + messageGroup + "] to 'forceComplete'.");
        }
        this.expireGroupScheduledFutures.put(
            UUIDConverter.getUUID(messageGroup.getGroupId()), scheduledFuture);
      } else {
        this.forceReleaseProcessor.processMessageGroup(messageGroup);
      }
    }
  }
  @Test
  public void testRemoveMessageGroup() throws Exception {
    GemfireMessageStore store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();
    MessageGroup messageGroup = store.getMessageGroup(1);
    Message<?> message = new GenericMessage<String>("Hello");
    messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message);
    assertEquals(1, messageGroup.size());

    store.removeMessageGroup(1);
    MessageGroup messageGroupA = store.getMessageGroup(1);
    assertNotSame(messageGroup, messageGroupA);
    assertEquals(0, messageGroupA.getMessages().size());
    assertEquals(0, messageGroupA.size());

    // make sure the store is properly rebuild from Gemfire
    store = new GemfireMessageStore(this.cache);
    store.afterPropertiesSet();

    messageGroup = store.getMessageGroup(1);

    assertEquals(0, messageGroup.getMessages().size());
    assertEquals(0, messageGroup.size());
  }
  @Test
  @RedisAvailable
  public void testDelayerHandlerRescheduleWithRedisMessageStore() throws Exception {
    AbstractApplicationContext context =
        new ClassPathXmlApplicationContext(
            "DelayerHandlerRescheduleIntegrationTests-context.xml", this.getClass());
    MessageChannel input = context.getBean("input", MessageChannel.class);
    MessageGroupStore messageStore = context.getBean("messageStore", MessageGroupStore.class);

    String delayerMessageGroupId = DELAYER_ID + ".messageGroupId";

    messageStore.removeMessageGroup(delayerMessageGroupId);

    Message<String> message1 = MessageBuilder.withPayload("test1").build();
    input.send(message1);
    input.send(MessageBuilder.withPayload("test2").build());

    // Emulate restart and check DB state before next start
    // Interrupt taskScheduler as quickly as possible
    ThreadPoolTaskScheduler taskScheduler =
        (ThreadPoolTaskScheduler) IntegrationContextUtils.getTaskScheduler(context);
    taskScheduler.shutdown();
    taskScheduler.getScheduledExecutor().awaitTermination(10, TimeUnit.SECONDS);
    context.destroy();

    try {
      context.getBean("input", MessageChannel.class);
      fail("IllegalStateException expected");
    } catch (Exception e) {
      assertTrue(e instanceof IllegalStateException);
      assertTrue(
          e.getMessage()
              .contains("BeanFactory not initialized or already closed - call 'refresh'"));
    }

    assertEquals(1, messageStore.getMessageGroupCount());
    assertEquals(delayerMessageGroupId, messageStore.iterator().next().getGroupId());
    assertEquals(2, messageStore.messageGroupSize(delayerMessageGroupId));
    assertEquals(2, messageStore.getMessageCountForAllMessageGroups());
    MessageGroup messageGroup = messageStore.getMessageGroup(delayerMessageGroupId);
    Message<?> messageInStore = messageGroup.getMessages().iterator().next();
    Object payload = messageInStore.getPayload();

    // INT-3049
    assertTrue(payload instanceof DelayHandler.DelayedMessageWrapper);
    assertEquals(message1, ((DelayHandler.DelayedMessageWrapper) payload).getOriginal());

    context.refresh();

    PollableChannel output = context.getBean("output", PollableChannel.class);

    Message<?> message = output.receive(20000);
    assertNotNull(message);

    Object payload1 = message.getPayload();

    message = output.receive(20000);
    assertNotNull(message);
    Object payload2 = message.getPayload();
    assertNotSame(payload1, payload2);

    assertEquals(1, messageStore.getMessageGroupCount());
    assertEquals(0, messageStore.messageGroupSize(delayerMessageGroupId));

    messageStore.removeMessageGroup(delayerMessageGroupId);
  }
  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");
    }
  }
 void remove(MessageGroup group) {
   Object correlationKey = group.getGroupId();
   messageStore.removeMessageGroup(correlationKey);
 }