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