From a322f5c35d091b8a2a0917c84a3335c79927ff9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6glund?= Date: Fri, 20 May 2022 08:59:37 -0400 Subject: [PATCH] Move spring-integration-hazelcast from extensions * Update javadocs * Upgrade from log4j to log4j2 * Remove unnecessary test deps. * Change from implmementation to api * Remove unneeded test deps. * Fix HZ XSD to be "versionless" * Use `com.google.code.findbugs:annotations` dep to avoid warning from HZ code base --- build.gradle | 15 +- .../integration/hazelcast/CacheEventType.java | 66 +++ .../hazelcast/CacheListeningPolicyType.java | 41 ++ .../hazelcast/ClusterMonitorType.java | 58 +++ .../DistributedSQLIterationType.java | 52 ++ .../hazelcast/HazelcastHeaders.java | 51 ++ ...zelcastIntegrationDefinitionValidator.java | 108 ++++ .../HazelcastLocalInstanceRegistrar.java | 120 +++++ ...stIntegrationConfigurationInitializer.java | 45 ++ .../hazelcast/config/package-info.java | 4 + ...terMonitorInboundChannelAdapterParser.java | 98 ++++ ...nuousQueryInboundChannelAdapterParser.java | 108 ++++ ...ributedSQLInboundChannelAdapterParser.java | 68 +++ ...ventDrivenInboundChannelAdapterParser.java | 99 ++++ .../HazelcastIntegrationNamespaceHandler.java | 38 ++ ...HazelcastOutboundChannelAdapterParser.java | 71 +++ .../hazelcast/config/xml/package-info.java | 4 + .../AbstractHazelcastMessageProducer.java | 232 +++++++++ ...azelcastClusterMonitorMessageProducer.java | 213 ++++++++ ...zelcastContinuousQueryMessageProducer.java | 68 +++ .../HazelcastDistributedSQLMessageSource.java | 92 ++++ .../HazelcastEventDrivenMessageProducer.java | 197 ++++++++ .../hazelcast/inbound/package-info.java | 4 + .../hazelcast/leader/LeaderInitiator.java | 429 ++++++++++++++++ .../hazelcast/leader/package-info.java | 4 + .../listener/HazelcastMembershipListener.java | 88 ++++ .../hazelcast/listener/package-info.java | 4 + .../hazelcast/lock/HazelcastLockRegistry.java | 46 ++ .../hazelcast/lock/package-info.java | 4 + .../message/EntryEventMessagePayload.java | 86 ++++ .../hazelcast/message/package-info.java | 4 + .../metadata/HazelcastMetadataStore.java | 140 +++++ .../hazelcast/metadata/package-info.java | 4 + .../HazelcastCacheWritingMessageHandler.java | 167 ++++++ .../hazelcast/outbound/package-info.java | 4 + .../integration/hazelcast/package-info.java | 4 + .../store/HazelcastMessageStore.java | 83 +++ .../hazelcast/store/package-info.java | 4 + .../main/resources/META-INF/spring.factories | 2 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 4 + .../xml/spring-integration-hazelcast.xsd | 303 +++++++++++ ...rationDefinitionValidatorTests-context.xml | 19 + ...stIntegrationDefinitionValidatorTests.java | 129 +++++ .../HazelcastIntegrationTestUser.java | 116 +++++ .../HazelcastTestRequestHandlerAdvice.java | 50 ++ .../integration/hazelcast/common-config.xml | 18 + ...dMapInboundChannelAdapterTests-context.xml | 71 +++ ...tributedMapInboundChannelAdapterTests.java | 165 ++++++ ...itorInboundChannelAdapterTests-context.xml | 170 +++++++ ...sterMonitorInboundChannelAdapterTests.java | 113 +++++ ...ivenInboundChannelAdapterTests-context.xml | 46 ++ ...EventDrivenInboundChannelAdapterTests.java | 108 ++++ ...ivenInboundChannelAdapterTests-context.xml | 56 ++ ...EventDrivenInboundChannelAdapterTests.java | 152 ++++++ ...ivenInboundChannelAdapterTests-context.xml | 47 ++ ...EventDrivenInboundChannelAdapterTests.java | 113 +++++ ...dSQLInboundChannelAdapterTests-context.xml | 67 +++ ...tributedSQLInboundChannelAdapterTests.java | 98 ++++ ...ivenInboundChannelAdapterTests-context.xml | 44 ++ ...EventDrivenInboundChannelAdapterTests.java | 113 +++++ ...ivenInboundChannelAdapterTests-context.xml | 25 + ...EventDrivenInboundChannelAdapterTests.java | 62 +++ ...ivenInboundChannelAdapterTests-context.xml | 44 ++ ...EventDrivenInboundChannelAdapterTests.java | 133 +++++ ...ivenInboundChannelAdapterTests-context.xml | 55 ++ ...EventDrivenInboundChannelAdapterTests.java | 169 +++++++ ...edMapInboundChannelAdapterConfigTests.java | 85 ++++ ...nitorInboundChannelAdapterConfigTests.java | 59 +++ ...edSQLInboundChannelAdapterConfigTests.java | 97 ++++ ...rivenInboundChannelAdapterConfigTests.java | 154 ++++++ ...stIntegrationInboundTestConfiguration.java | 402 +++++++++++++++ ...zelcastInboundChannelAdapterTestUtils.java | 370 ++++++++++++++ .../leader/LeaderInitiatorTests.java | 311 ++++++++++++ .../lock/HazelcastLockRegistryTests.java | 276 ++++++++++ .../metadata/HazelcastMetadataStoreTests.java | 181 +++++++ ...astOutboundChannelAdapterTests-context.xml | 280 ++++++++++ .../HazelcastOutboundChannelAdapterTests.java | 477 ++++++++++++++++++ ...tIntegrationOutboundTestConfiguration.java | 310 ++++++++++++ ...castOutboundChannelAdapterConfigTests.java | 204 ++++++++ ...elcastOutboundChannelAdapterTestUtils.java | 248 +++++++++ .../store/HazelcastMessageStoreTests.java | 149 ++++++ .../src/test/resources/log4j2-test.xml | 16 + 83 files changed, 9032 insertions(+), 3 deletions(-) create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheEventType.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheListeningPolicyType.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/ClusterMonitorType.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/DistributedSQLIterationType.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastHeaders.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidator.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastLocalInstanceRegistrar.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/HazelcastIntegrationConfigurationInitializer.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastClusterMonitorInboundChannelAdapterParser.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastContinuousQueryInboundChannelAdapterParser.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastDistributedSQLInboundChannelAdapterParser.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastEventDrivenInboundChannelAdapterParser.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastIntegrationNamespaceHandler.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastOutboundChannelAdapterParser.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/AbstractHazelcastMessageProducer.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorMessageProducer.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastContinuousQueryMessageProducer.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLMessageSource.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastEventDrivenMessageProducer.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/LeaderInitiator.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/HazelcastMembershipListener.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistry.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/EntryEventMessagePayload.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStore.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/HazelcastCacheWritingMessageHandler.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/package-info.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/HazelcastMessageStore.java create mode 100644 spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/package-info.java create mode 100644 spring-integration-hazelcast/src/main/resources/META-INF/spring.factories create mode 100644 spring-integration-hazelcast/src/main/resources/META-INF/spring.handlers create mode 100644 spring-integration-hazelcast/src/main/resources/META-INF/spring.schemas create mode 100644 spring-integration-hazelcast/src/main/resources/org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationTestUser.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastTestRequestHandlerAdvice.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/common-config.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastCQDistributedMapInboundChannelAdapterConfigTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastClusterMonitorInboundChannelAdapterConfigTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastDistributedSQLInboundChannelAdapterConfigTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastEventDrivenInboundChannelAdapterConfigTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastIntegrationInboundTestConfiguration.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/util/HazelcastInboundChannelAdapterTestUtils.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistryTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStoreTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests-context.xml create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastIntegrationOutboundTestConfiguration.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/util/HazelcastOutboundChannelAdapterTestUtils.java create mode 100644 spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/store/HazelcastMessageStoreTests.java create mode 100644 spring-integration-hazelcast/src/test/resources/log4j2-test.xml diff --git a/build.gradle b/build.gradle index 9b0f2d57e4..c8d0932ef5 100644 --- a/build.gradle +++ b/build.gradle @@ -59,8 +59,8 @@ ext { commonsNetVersion = '3.8.0' curatorVersion = '4.3.0' derbyVersion = '10.14.2.0' + findbugsVersion = '3.0.1' ftpServerVersion = '1.2.0' - googleJsr305Version = '3.0.2' greenmailVersion = '2.0.0-alpha-2' groovyVersion = '3.0.10' hamcrestVersion = '2.2' @@ -251,8 +251,8 @@ configure(javaProjects) { subproject -> } // JSR-305 only used for non-required meta-annotations - compileOnly "com.google.code.findbugs:jsr305:$googleJsr305Version" - testImplementation "com.google.code.findbugs:jsr305:$googleJsr305Version" + compileOnly "com.google.code.findbugs:annotations:$findbugsVersion" + testImplementation "com.google.code.findbugs:annotations:$findbugsVersion" testImplementation("org.awaitility:awaitility:$awaitilityVersion") { exclude group: 'org.hamcrest' } @@ -1080,6 +1080,15 @@ project('spring-integration-bom') { } } + +project('spring-integration-hazelcast') { + description = 'Spring Integration Hazelcast Support' + dependencies { + api "com.hazelcast:hazelcast:$hazelcastVersion" + api project(':spring-integration-core') + } +} + sonarqube { properties { property 'sonar.links.homepage', linkHomepage diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheEventType.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheEventType.java new file mode 100644 index 0000000000..455d4def81 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheEventType.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +/** + * Enumeration of Cache Event Types. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + * + * @see org.springframework.integration.hazelcast.inbound.AbstractHazelcastMessageProducer + */ +public enum CacheEventType { + + /** + * The Hazelcast ADDED event. + */ + ADDED, + + /** + * The Hazelcast REMOVED event. + */ + REMOVED, + + /** + * The Hazelcast UPDATED event. + */ + UPDATED, + + /** + * The Hazelcast EVICTED event. + */ + EVICTED, + + /** + * The Hazelcast EXPIRED event. + */ + EXPIRED, + + /** + * The Hazelcast EVICT_ALL event. + */ + EVICT_ALL, + + /** + * The Hazelcast CLEAR_ALL event. + */ + CLEAR_ALL + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheListeningPolicyType.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheListeningPolicyType.java new file mode 100644 index 0000000000..5eeceab504 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/CacheListeningPolicyType.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +/** + * Enumeration of Cache Listening Policy Type. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + * + * @see org.springframework.integration.hazelcast.inbound.AbstractHazelcastMessageProducer + */ +public enum CacheListeningPolicyType { + + /** + * Only the local Hazelcast node can accept event. + */ + SINGLE, + + /** + * All subscribed members can accept event. + */ + ALL + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/ClusterMonitorType.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/ClusterMonitorType.java new file mode 100644 index 0000000000..aac0910a9b --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/ClusterMonitorType.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +/** + * Enumeration of Hazelcast Cluster Monitor Types. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + * + * @see org.springframework.integration.hazelcast.inbound.HazelcastClusterMonitorMessageProducer + * @see com.hazelcast.core.DistributedObjectListener + * @see com.hazelcast.core.LifecycleListener + */ +public enum ClusterMonitorType { + + /** + * The membership listener mode. + */ + MEMBERSHIP, + + /** + * The distributed object listener mode. + */ + DISTRIBUTED_OBJECT, + + /** + * The migration listener mode. + */ + MIGRATION, + + /** + * The listener listener mode. + */ + LIFECYCLE, + + /** + * The client listener mode. + */ + CLIENT + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/DistributedSQLIterationType.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/DistributedSQLIterationType.java new file mode 100644 index 0000000000..ca3a99bc3d --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/DistributedSQLIterationType.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +/** + * Enumeration of Distributed SQL Iteration Type. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + * + * @see org.springframework.integration.hazelcast.inbound.HazelcastDistributedSQLMessageSource + * @see com.hazelcast.map.IMap + */ +public enum DistributedSQLIterationType { + + /** + * The {@link com.hazelcast.map.IMap#entrySet()} to iterate. + */ + ENTRY, + + /** + * The {@link com.hazelcast.map.IMap#keySet()} to iterate. + */ + KEY, + + /** + * The {@link com.hazelcast.map.IMap#localKeySet()} to iterate. + */ + LOCAL_KEY, + + /** + * The {@link com.hazelcast.map.IMap#values()} to iterate. + */ + VALUE + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastHeaders.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastHeaders.java new file mode 100644 index 0000000000..6fedd518dc --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastHeaders.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +/** + * Hazelcast Message Headers. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public abstract class HazelcastHeaders { + + private static final String PREFIX = "hazelcast_"; + + /** + * The event type header name. + */ + public static final String EVENT_TYPE = PREFIX + "eventType"; + + /** + * The member header name. + */ + public static final String MEMBER = PREFIX + "member"; + + /** + * The cache name header name. + */ + public static final String CACHE_NAME = PREFIX + "cacheName"; + + /** + * The publishing time header name. + */ + public static final String PUBLISHING_TIME = PREFIX + "publishingTime"; + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidator.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidator.java new file mode 100644 index 0000000000..9092ebdcce --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidator.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import com.hazelcast.collection.IList; +import com.hazelcast.collection.IQueue; +import com.hazelcast.collection.ISet; +import com.hazelcast.core.DistributedObject; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Common Validator for Hazelcast Integration. It validates cache types and events. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public final class HazelcastIntegrationDefinitionValidator { + + public static > Set validateEnumType(final Class enumType, final String types) { + Set typeSet = StringUtils.commaDelimitedListToSet(StringUtils.trimAllWhitespace(types)); + for (String type : typeSet) { + Enum.valueOf(enumType, type); + } + + return typeSet; + } + + public static void validateCacheTypeForEventDrivenMessageProducer(final DistributedObject distributedObject) { + if (!(distributedObject instanceof IMap + || distributedObject instanceof MultiMap + || distributedObject instanceof ReplicatedMap + || distributedObject instanceof IList + || distributedObject instanceof ISet + || distributedObject instanceof IQueue + || distributedObject instanceof ITopic)) { + throw new IllegalArgumentException( + "Invalid 'cache' type is set. IMap, MultiMap, ReplicatedMap, IList, ISet, IQueue and ITopic" + + " cache object types are acceptable for Hazelcast Inbound Channel Adapter."); + } + } + + public static void validateCacheEventsByDistributedObject( + final DistributedObject distributedObject, final Set cacheEventTypeSet) { + List supportedCacheEventTypes = getSupportedCacheEventTypes(distributedObject); + if (!CollectionUtils.isEmpty(supportedCacheEventTypes)) { + validateCacheEventsByDistributedObject(distributedObject, cacheEventTypeSet, supportedCacheEventTypes); + } + } + + private static List getSupportedCacheEventTypes(final DistributedObject distributedObject) { + if ((distributedObject instanceof IList) + || (distributedObject instanceof ISet) + || (distributedObject instanceof IQueue)) { + return Arrays.asList(CacheEventType.ADDED.toString(), CacheEventType.REMOVED.toString()); + } + else if (distributedObject instanceof MultiMap) { + return Arrays.asList(CacheEventType.ADDED.toString(), + CacheEventType.REMOVED.toString(), + CacheEventType.CLEAR_ALL.toString()); + } + else if (distributedObject instanceof ReplicatedMap) { + return Arrays.asList(CacheEventType.ADDED.toString(), + CacheEventType.REMOVED.toString(), + CacheEventType.UPDATED.toString(), + CacheEventType.EVICTED.toString()); + } + + return null; + } + + private static void validateCacheEventsByDistributedObject(DistributedObject distributedObject, + Set cacheEventTypeSet, List supportedCacheEventTypes) { + if (!supportedCacheEventTypes.containsAll(cacheEventTypeSet)) { + throw new IllegalArgumentException("'cache-events' attribute of " + + distributedObject.getName() + " can be set as " + supportedCacheEventTypes); + } + } + + private HazelcastIntegrationDefinitionValidator() { + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastLocalInstanceRegistrar.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastLocalInstanceRegistrar.java new file mode 100644 index 0000000000..8ead3df839 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/HazelcastLocalInstanceRegistrar.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +import java.net.SocketAddress; +import java.util.concurrent.locks.Lock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.integration.hazelcast.listener.HazelcastMembershipListener; + +import com.hazelcast.cluster.MembershipListener; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.multimap.MultiMap; + +/** + * This class creates an internal configuration {@link MultiMap} to cache Hazelcast instances' socket + * address information which used Hazelcast event-driven inbound channel adapter(s). It + * also enables a Hazelcast {@link MembershipListener} to listen for + * membership updates. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastLocalInstanceRegistrar implements SmartInitializingSingleton { + + private static final Log logger = LogFactory.getLog(HazelcastLocalInstanceRegistrar.class); + + /** + * The bean name for the {@link HazelcastLocalInstanceRegistrar} instance. + */ + public static final String BEAN_NAME = "hazelcastLocalInstanceRegistrar"; + + /** + * The name for the Hazelcast MultiMap used for membership registration. + */ + public static final String SPRING_INTEGRATION_INTERNAL_CLUSTER_MULTIMAP = + "SPRING_INTEGRATION_INTERNAL_CLUSTER_MULTIMAP"; + + /** + * The name for the Hazelcast Lock used for membership registration. + */ + public static final String SPRING_INTEGRATION_INTERNAL_CLUSTER_LOCK = "SPRING_INTEGRATION_INTERNAL_CLUSTER_LOCK"; + + private final HazelcastInstance hazelcastInstance; + + /** + * Construct {@link HazelcastLocalInstanceRegistrar} based on the local JVM {@link HazelcastInstance}s if any. + */ + public HazelcastLocalInstanceRegistrar() { + this.hazelcastInstance = null; + } + + /** + * Construct {@link HazelcastLocalInstanceRegistrar} based on the provided {@link HazelcastInstance}. + * @param hazelcastInstance the {@link HazelcastInstance} to use. + */ + public HazelcastLocalInstanceRegistrar(HazelcastInstance hazelcastInstance) { + this.hazelcastInstance = hazelcastInstance; + } + + @Override + public void afterSingletonsInstantiated() { + if (this.hazelcastInstance == null) { + if (!Hazelcast.getAllHazelcastInstances().isEmpty()) { + HazelcastInstance hazelcastInstance = Hazelcast.getAllHazelcastInstances().iterator().next(); + hazelcastInstance.getCluster().addMembershipListener(new HazelcastMembershipListener()); + syncConfigurationMultiMap(hazelcastInstance); + } + else { + logger.warn("No HazelcastInstances for MembershipListener registration"); + } + } + else { + syncConfigurationMultiMap(this.hazelcastInstance); + this.hazelcastInstance.getCluster().addMembershipListener(new HazelcastMembershipListener()); + } + } + + private void syncConfigurationMultiMap(HazelcastInstance hazelcastInstance) { + Lock lock = hazelcastInstance.getCPSubsystem().getLock(SPRING_INTEGRATION_INTERNAL_CLUSTER_LOCK); + lock.lock(); + try { + MultiMap multiMap = hazelcastInstance + .getMultiMap(SPRING_INTEGRATION_INTERNAL_CLUSTER_MULTIMAP); + for (HazelcastInstance localInstance : Hazelcast.getAllHazelcastInstances()) { + SocketAddress localInstanceSocketAddress = localInstance.getLocalEndpoint().getSocketAddress(); + if (multiMap.size() == 0) { + multiMap.put(localInstanceSocketAddress, localInstanceSocketAddress); + } + else { + multiMap.put(multiMap.keySet().iterator().next(), localInstanceSocketAddress); + } + } + } + finally { + lock.unlock(); + } + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/HazelcastIntegrationConfigurationInitializer.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/HazelcastIntegrationConfigurationInitializer.java new file mode 100644 index 0000000000..db0bfb6772 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/HazelcastIntegrationConfigurationInitializer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.integration.config.IntegrationConfigurationInitializer; +import org.springframework.integration.hazelcast.HazelcastLocalInstanceRegistrar; + +/** + * The Hazelcast Integration infrastructure {@code beanFactory} initializer. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastIntegrationConfigurationInitializer implements IntegrationConfigurationInitializer { + + @Override + public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { + BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory; + if (!beanDefinitionRegistry.containsBeanDefinition(HazelcastLocalInstanceRegistrar.BEAN_NAME)) { + beanDefinitionRegistry.registerBeanDefinition(HazelcastLocalInstanceRegistrar.BEAN_NAME, + new RootBeanDefinition(HazelcastLocalInstanceRegistrar.class)); + } + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/package-info.java new file mode 100644 index 0000000000..450c9c0dde --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes for configuration. + */ +package org.springframework.integration.hazelcast.config; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastClusterMonitorInboundChannelAdapterParser.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastClusterMonitorInboundChannelAdapterParser.java new file mode 100644 index 0000000000..120c9c269b --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastClusterMonitorInboundChannelAdapterParser.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.hazelcast.inbound.HazelcastClusterMonitorMessageProducer; +import org.springframework.util.StringUtils; + +/** + * Parser for the {@code } component. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastClusterMonitorInboundChannelAdapterParser extends + AbstractSingleBeanDefinitionParser { + + private static final String CHANNEL_ATTRIBUTE = "channel"; + + private static final String HAZELCAST_INSTANCE_ATTRIBUTE = "hazelcast-instance"; + + private static final String MONITOR_TYPES_ATTRIBUTE = "monitor-types"; + + private static final String OUTPUT_CHANNEL = "outputChannel"; + + private static final String MONITOR_EVENT_TYPES = "monitorEventTypes"; + + @Override + protected Class getBeanClass(Element element) { + return HazelcastClusterMonitorMessageProducer.class; + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, + ParserContext parserContext) throws BeanDefinitionStoreException { + String id = super.resolveId(element, definition, parserContext); + + if (!element.hasAttribute(CHANNEL_ATTRIBUTE)) { + id = id + ".adapter"; + } + + if (!StringUtils.hasText(id)) { + id = BeanDefinitionReaderUtils.generateBeanName(definition, + parserContext.getRegistry()); + } + + return id; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + String channelName = element.getAttribute(CHANNEL_ATTRIBUTE); + if (!StringUtils.hasText(channelName)) { + channelName = IntegrationNamespaceUtils.createDirectChannel(element, + parserContext); + } + + if (!StringUtils.hasText(element.getAttribute(HAZELCAST_INSTANCE_ATTRIBUTE))) { + parserContext.getReaderContext().error( + "'" + HAZELCAST_INSTANCE_ATTRIBUTE + "' attribute is required.", + element); + } + + builder.addPropertyReference(OUTPUT_CHANNEL, channelName); + builder.addConstructorArgReference(element + .getAttribute(HAZELCAST_INSTANCE_ATTRIBUTE)); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, + MONITOR_TYPES_ATTRIBUTE, MONITOR_EVENT_TYPES); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, + IntegrationNamespaceUtils.AUTO_STARTUP); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, + IntegrationNamespaceUtils.PHASE); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastContinuousQueryInboundChannelAdapterParser.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastContinuousQueryInboundChannelAdapterParser.java new file mode 100644 index 0000000000..7ae37d48e2 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastContinuousQueryInboundChannelAdapterParser.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.hazelcast.inbound.HazelcastContinuousQueryMessageProducer; +import org.springframework.util.StringUtils; + +/** + * Hazelcast Continuous Query Inbound Channel Adapter Parser parses + * {@code } configuration. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastContinuousQueryInboundChannelAdapterParser extends AbstractSingleBeanDefinitionParser { + + private static final String CHANNEL_ATTRIBUTE = "channel"; + + private static final String CACHE_ATTRIBUTE = "cache"; + + private static final String CACHE_EVENTS_ATTRIBUTE = "cache-events"; + + private static final String PREDICATE_ATTRIBUTE = "predicate"; + + private static final String INCLUDE_VALUE_ATTRIBUTE = "include-value"; + + private static final String CACHE_LISTENING_POLICY_ATTRIBUTE = "cache-listening-policy"; + + private static final String OUTPUT_CHANNEL = "outputChannel"; + + private static final String CACHE_EVENT_TYPES = "cacheEventTypes"; + + @Override + protected Class getBeanClass(Element element) { + return HazelcastContinuousQueryMessageProducer.class; + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + String id = super.resolveId(element, definition, parserContext); + + if (!element.hasAttribute(CHANNEL_ATTRIBUTE)) { + id = id + ".adapter"; + } + + if (!StringUtils.hasText(id)) { + id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); + } + + return id; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String channelName = element.getAttribute(CHANNEL_ATTRIBUTE); + if (!StringUtils.hasText(channelName)) { + channelName = IntegrationNamespaceUtils.createDirectChannel(element, parserContext); + } + + if (!StringUtils.hasText(element.getAttribute(CACHE_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(CACHE_EVENTS_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_EVENTS_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(PREDICATE_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + PREDICATE_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(CACHE_LISTENING_POLICY_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_LISTENING_POLICY_ATTRIBUTE + "' attribute is required.", + element); + } + + builder.addPropertyReference(OUTPUT_CHANNEL, channelName); + builder.addConstructorArgReference(element.getAttribute(CACHE_ATTRIBUTE)); + builder.addConstructorArgValue(element.getAttribute(PREDICATE_ATTRIBUTE)); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, CACHE_EVENTS_ATTRIBUTE, CACHE_EVENT_TYPES); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, INCLUDE_VALUE_ATTRIBUTE); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, CACHE_LISTENING_POLICY_ATTRIBUTE); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastDistributedSQLInboundChannelAdapterParser.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastDistributedSQLInboundChannelAdapterParser.java new file mode 100644 index 0000000000..ba485a027f --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastDistributedSQLInboundChannelAdapterParser.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.hazelcast.inbound.HazelcastDistributedSQLMessageSource; +import org.springframework.util.StringUtils; + +/** + * Hazelcast Distributed SQL Inbound Channel Adapter Parser parses + * {@code } configuration. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastDistributedSQLInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { + + private static final String CACHE_ATTRIBUTE = "cache"; + + private static final String DISTRIBUTED_SQL_ATTRIBUTE = "distributed-sql"; + + private static final String ITERATION_TYPE_ATTRIBUTE = "iteration-type"; + + @Override + protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { + if (!StringUtils.hasText(element.getAttribute(CACHE_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(DISTRIBUTED_SQL_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + DISTRIBUTED_SQL_ATTRIBUTE + "' attribute is required.", + element); + } + else if (!StringUtils.hasText(element.getAttribute(ITERATION_TYPE_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + ITERATION_TYPE_ATTRIBUTE + "' attribute is required.", + element); + } + + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(HazelcastDistributedSQLMessageSource.class.getName()); + + builder.addConstructorArgReference(element.getAttribute(CACHE_ATTRIBUTE)); + builder.addConstructorArgValue(element.getAttribute(DISTRIBUTED_SQL_ATTRIBUTE)); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, ITERATION_TYPE_ATTRIBUTE); + + return builder.getBeanDefinition(); + } +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastEventDrivenInboundChannelAdapterParser.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastEventDrivenInboundChannelAdapterParser.java new file mode 100644 index 0000000000..bbc23cb0f1 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastEventDrivenInboundChannelAdapterParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.hazelcast.inbound.HazelcastEventDrivenMessageProducer; +import org.springframework.util.StringUtils; + +/** + * Hazelcast Event Driven Inbound Channel Adapter Parser parses + * {@code } configuration. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastEventDrivenInboundChannelAdapterParser extends AbstractSingleBeanDefinitionParser { + + private static final String CHANNEL_ATTRIBUTE = "channel"; + + private static final String CACHE_ATTRIBUTE = "cache"; + + private static final String CACHE_EVENTS_ATTRIBUTE = "cache-events"; + + private static final String CACHE_LISTENING_POLICY_ATTRIBUTE = "cache-listening-policy"; + + private static final String OUTPUT_CHANNEL = "outputChannel"; + + private static final String CACHE_EVENT_TYPES = "cacheEventTypes"; + + @Override + protected Class getBeanClass(Element element) { + return HazelcastEventDrivenMessageProducer.class; + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + String id = super.resolveId(element, definition, parserContext); + + if (!element.hasAttribute(CHANNEL_ATTRIBUTE)) { + id = id + ".adapter"; + } + + if (!StringUtils.hasText(id)) { + id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); + } + + return id; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String channelName = element.getAttribute(CHANNEL_ATTRIBUTE); + if (!StringUtils.hasText(channelName)) { + channelName = IntegrationNamespaceUtils.createDirectChannel(element, parserContext); + } + + if (!StringUtils.hasText(element.getAttribute(CACHE_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(CACHE_EVENTS_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_EVENTS_ATTRIBUTE + "' attribute is required.", element); + } + else if (!StringUtils.hasText(element.getAttribute(CACHE_LISTENING_POLICY_ATTRIBUTE))) { + parserContext.getReaderContext().error("'" + CACHE_LISTENING_POLICY_ATTRIBUTE + "' attribute is required.", + element); + } + + builder.addPropertyReference(OUTPUT_CHANNEL, channelName); + builder.addConstructorArgReference(element.getAttribute(CACHE_ATTRIBUTE)); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, CACHE_EVENTS_ATTRIBUTE, CACHE_EVENT_TYPES); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, CACHE_LISTENING_POLICY_ATTRIBUTE); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastIntegrationNamespaceHandler.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastIntegrationNamespaceHandler.java new file mode 100644 index 0000000000..085c2f3f2c --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastIntegrationNamespaceHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * Namespace handler for the Hazelcast schema. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastIntegrationNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + @Override + public void init() { + registerBeanDefinitionParser("inbound-channel-adapter", new HazelcastEventDrivenInboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new HazelcastOutboundChannelAdapterParser()); + registerBeanDefinitionParser("cq-inbound-channel-adapter", new HazelcastContinuousQueryInboundChannelAdapterParser()); + registerBeanDefinitionParser("ds-inbound-channel-adapter", new HazelcastDistributedSQLInboundChannelAdapterParser()); + registerBeanDefinitionParser("cm-inbound-channel-adapter", new HazelcastClusterMonitorInboundChannelAdapterParser()); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastOutboundChannelAdapterParser.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastOutboundChannelAdapterParser.java new file mode 100644 index 0000000000..5bd0d41d64 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/HazelcastOutboundChannelAdapterParser.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.config.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.hazelcast.outbound.HazelcastCacheWritingMessageHandler; + +/** + * Hazelcast Outbound Channel Adapter Parser for + * {@code }. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + + private static final String CACHE_ATTRIBUTE = "cache"; + + private static final String CACHE_EXPRESSION_ATTRIBUTE = "cache-expression"; + + private static final String KEY_EXPRESSION_ATTRIBUTE = "key-expression"; + + private static final String EXTRACT_PAYLOAD_ATTRIBUTE = "extract-payload"; + + private static final String DISTRIBUTED_OBJECT = "distributedObject"; + + @Override + protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(HazelcastCacheWritingMessageHandler.class); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, CACHE_ATTRIBUTE, DISTRIBUTED_OBJECT); + BeanDefinition cacheExpressionDef = + IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(CACHE_EXPRESSION_ATTRIBUTE, element); + if (cacheExpressionDef != null) { + builder.addPropertyValue("cacheExpression", cacheExpressionDef); + } + + BeanDefinition keyExpressionDef = + IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(KEY_EXPRESSION_ATTRIBUTE, element); + if (keyExpressionDef != null) { + builder.addPropertyValue("keyExpression", keyExpressionDef); + } + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, EXTRACT_PAYLOAD_ATTRIBUTE); + + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/package-info.java new file mode 100644 index 0000000000..0973658e9c --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes for parsers and namespace handlers. + */ +package org.springframework.integration.hazelcast.config.xml; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/AbstractHazelcastMessageProducer.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/AbstractHazelcastMessageProducer.java new file mode 100644 index 0000000000..7a749a9f2a --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/AbstractHazelcastMessageProducer.java @@ -0,0 +1,232 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.integration.hazelcast.CacheEventType; +import org.springframework.integration.hazelcast.CacheListeningPolicyType; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationDefinitionValidator; +import org.springframework.integration.hazelcast.HazelcastLocalInstanceRegistrar; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +import com.hazelcast.core.DistributedObject; +import com.hazelcast.core.EntryEvent; +import com.hazelcast.core.EntryListener; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.instance.EndpointQualifier; +import com.hazelcast.map.AbstractIMapEvent; +import com.hazelcast.map.MapEvent; +import com.hazelcast.multimap.MultiMap; + +/** + * Hazelcast Base Event-Driven Message Producer. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public abstract class AbstractHazelcastMessageProducer extends MessageProducerSupport { + + protected final DistributedObject distributedObject; + + private volatile CacheListeningPolicyType cacheListeningPolicy = CacheListeningPolicyType.SINGLE; + + private volatile UUID hazelcastRegisteredEventListenerId; + + private Set cacheEvents = Collections.singleton(CacheEventType.ADDED.name()); + + public AbstractHazelcastMessageProducer(DistributedObject distributedObject) { + Assert.notNull(distributedObject, "'distributedObject' must not be null"); + this.distributedObject = distributedObject; + } + + protected Set getCacheEvents() { + return this.cacheEvents; + } + + public void setCacheEventTypes(String cacheEventTypes) { + Set cacheEvents = + HazelcastIntegrationDefinitionValidator.validateEnumType(CacheEventType.class, cacheEventTypes); + Assert.notEmpty(cacheEvents, "'cacheEvents' must have elements"); + HazelcastIntegrationDefinitionValidator.validateCacheEventsByDistributedObject(this.distributedObject, + cacheEvents); + this.cacheEvents = cacheEvents; + } + + protected CacheListeningPolicyType getCacheListeningPolicy() { + return this.cacheListeningPolicy; + } + + public void setCacheListeningPolicy(CacheListeningPolicyType cacheListeningPolicy) { + Assert.notNull(cacheListeningPolicy, "'cacheListeningPolicy' must not be null"); + this.cacheListeningPolicy = cacheListeningPolicy; + } + + protected UUID getHazelcastRegisteredEventListenerId() { + return this.hazelcastRegisteredEventListenerId; + } + + protected void setHazelcastRegisteredEventListenerId(UUID hazelcastRegisteredEventListenerId) { + this.hazelcastRegisteredEventListenerId = hazelcastRegisteredEventListenerId; + } + + /** + * A base event listener abstraction. + * + * @param the event type + */ + protected abstract class AbstractHazelcastEventListener { + + protected abstract void processEvent(E event); + + protected abstract Message toMessage(E event); + + protected void sendMessage(E event, InetSocketAddress socketAddress, + CacheListeningPolicyType cacheListeningPolicyType) { + if (CacheListeningPolicyType.ALL == cacheListeningPolicyType || isEventAcceptable(socketAddress)) { + AbstractHazelcastMessageProducer.this.sendMessage(toMessage(event)); + } + } + + private boolean isEventAcceptable(final InetSocketAddress socketAddress) { + final Set hazelcastInstanceSet = Hazelcast.getAllHazelcastInstances(); + final Set localSocketAddressesSet = getLocalSocketAddresses(hazelcastInstanceSet); + return localSocketAddressesSet.isEmpty() || + localSocketAddressesSet.contains(socketAddress) + || isEventComingFromNonRegisteredHazelcastInstance(hazelcastInstanceSet.iterator().next(), + localSocketAddressesSet, socketAddress); + + } + + private Set getLocalSocketAddresses(final Set hazelcastInstanceSet) { + final Set localSocketAddressesSet = new HashSet<>(); + for (HazelcastInstance hazelcastInstance : hazelcastInstanceSet) { + localSocketAddressesSet.add(hazelcastInstance.getLocalEndpoint().getSocketAddress()); + } + + return localSocketAddressesSet; + } + + private boolean isEventComingFromNonRegisteredHazelcastInstance( + final HazelcastInstance hazelcastInstance, + final Set localSocketAddressesSet, + final InetSocketAddress socketAddressOfEvent) { + final MultiMap configMultiMap = hazelcastInstance + .getMultiMap(HazelcastLocalInstanceRegistrar.SPRING_INTEGRATION_INTERNAL_CLUSTER_MULTIMAP); + return configMultiMap.size() > 0 + && !configMultiMap.values().contains(socketAddressOfEvent) + && localSocketAddressesSet.contains(configMultiMap.keySet().iterator().next()); + + } + + } + + /** + * The {@link AbstractHazelcastEventListener} implementation for the {@link AbstractIMapEvent}s. + * + * @param the entry key type + * @param the entry value type + */ + protected final class HazelcastEntryListener extends + AbstractHazelcastEventListener implements EntryListener { + + @Override + public void entryAdded(EntryEvent event) { + processEvent(event); + } + + @Override + public void entryRemoved(EntryEvent event) { + processEvent(event); + } + + @Override + public void entryUpdated(EntryEvent event) { + processEvent(event); + } + + @Override + public void entryEvicted(EntryEvent event) { + processEvent(event); + } + + @Override + public void entryExpired(EntryEvent event) { + processEvent(event); + } + + @Override + public void mapEvicted(MapEvent event) { + processEvent(event); + } + + @Override + public void mapCleared(MapEvent event) { + processEvent(event); + } + + @Override + protected void processEvent(AbstractIMapEvent event) { + if (getCacheEvents().contains(event.getEventType().toString())) { + if (AbstractHazelcastMessageProducer.this.logger.isDebugEnabled()) { + AbstractHazelcastMessageProducer.this.logger.debug("Received Event : " + event); + } + sendMessage(event, + event.getMember().getSocketAddress(EndpointQualifier.MEMBER), getCacheListeningPolicy()); + } + } + + @Override + protected Message toMessage(AbstractIMapEvent event) { + final Map headers = new HashMap<>(); + headers.put(HazelcastHeaders.EVENT_TYPE, event.getEventType().name()); + headers.put(HazelcastHeaders.MEMBER, event.getMember().getSocketAddress(EndpointQualifier.MEMBER)); + headers.put(HazelcastHeaders.CACHE_NAME, event.getName()); + + if (event instanceof EntryEvent) { + @SuppressWarnings("unchecked") + EntryEvent entryEvent = (EntryEvent) event; + EntryEventMessagePayload messagePayload = new EntryEventMessagePayload<>(entryEvent.getKey(), + entryEvent.getValue(), entryEvent.getOldValue()); + return getMessageBuilderFactory().withPayload(messagePayload).copyHeaders(headers).build(); + } + else if (event instanceof MapEvent) { + return getMessageBuilderFactory() + .withPayload(((MapEvent) event).getNumberOfEntriesAffected()).copyHeaders(headers).build(); + } + else { + throw new IllegalStateException("Invalid event is received. Event : " + event); + } + } + + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorMessageProducer.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorMessageProducer.java new file mode 100644 index 0000000000..309de1a2fe --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorMessageProducer.java @@ -0,0 +1,213 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.integration.hazelcast.ClusterMonitorType; +import org.springframework.integration.hazelcast.HazelcastIntegrationDefinitionValidator; +import org.springframework.util.Assert; + +import com.hazelcast.client.Client; +import com.hazelcast.client.ClientListener; +import com.hazelcast.cluster.MembershipEvent; +import com.hazelcast.cluster.MembershipListener; +import com.hazelcast.core.DistributedObjectEvent; +import com.hazelcast.core.DistributedObjectListener; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.LifecycleEvent; +import com.hazelcast.core.LifecycleListener; +import com.hazelcast.partition.MigrationListener; +import com.hazelcast.partition.MigrationState; +import com.hazelcast.partition.ReplicaMigrationEvent; + +/** + * Hazelcast Cluster Monitor Event Driven Message Producer is a message producer which + * enables {@link HazelcastClusterMonitorMessageProducer.HazelcastClusterMonitorListener} + * listener in order to listen cluster related events and sends events to related channel. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastClusterMonitorMessageProducer extends MessageProducerSupport { + + private final HazelcastInstance hazelcastInstance; + + private Set monitorTypes = Collections.singleton(ClusterMonitorType.MEMBERSHIP.name()); + + private final Map hazelcastRegisteredListenerIdMap = new ConcurrentHashMap<>(5); + + public HazelcastClusterMonitorMessageProducer(HazelcastInstance hazelcastInstance) { + Assert.notNull(hazelcastInstance, "'hazelcastInstance' must not be null"); + this.hazelcastInstance = hazelcastInstance; + } + + public void setMonitorEventTypes(String monitorEventTypes) { + final Set monitorTypes = + HazelcastIntegrationDefinitionValidator.validateEnumType(ClusterMonitorType.class, monitorEventTypes); + Assert.notEmpty(monitorTypes, "'monitorTypes' must have elements"); + this.monitorTypes = monitorTypes; + } + + @Override + protected void doStart() { + final HazelcastClusterMonitorListener clusterMonitorListener = new HazelcastClusterMonitorListener(); + + if (this.monitorTypes.contains(ClusterMonitorType.MEMBERSHIP.name())) { + final UUID registrationId = this.hazelcastInstance.getCluster() + .addMembershipListener(clusterMonitorListener); + this.hazelcastRegisteredListenerIdMap.put(ClusterMonitorType.MEMBERSHIP, registrationId); + } + + if (this.monitorTypes.contains(ClusterMonitorType.DISTRIBUTED_OBJECT.name())) { + final UUID registrationId = this.hazelcastInstance + .addDistributedObjectListener(clusterMonitorListener); + this.hazelcastRegisteredListenerIdMap.put(ClusterMonitorType.DISTRIBUTED_OBJECT, registrationId); + } + + if (this.monitorTypes.contains(ClusterMonitorType.MIGRATION.name())) { + final UUID registrationId = this.hazelcastInstance.getPartitionService() + .addMigrationListener(clusterMonitorListener); + this.hazelcastRegisteredListenerIdMap.put(ClusterMonitorType.MIGRATION, registrationId); + } + + if (this.monitorTypes.contains(ClusterMonitorType.LIFECYCLE.name())) { + final UUID registrationId = this.hazelcastInstance.getLifecycleService() + .addLifecycleListener(clusterMonitorListener); + this.hazelcastRegisteredListenerIdMap.put(ClusterMonitorType.LIFECYCLE, registrationId); + } + + if (this.monitorTypes.contains(ClusterMonitorType.CLIENT.name())) { + final UUID registrationId = this.hazelcastInstance.getClientService() + .addClientListener(clusterMonitorListener); + this.hazelcastRegisteredListenerIdMap.put(ClusterMonitorType.CLIENT, registrationId); + } + } + + @Override + protected void doStop() { + if (this.hazelcastInstance.getLifecycleService().isRunning()) { + UUID id = this.hazelcastRegisteredListenerIdMap.remove(ClusterMonitorType.MEMBERSHIP); + if (id != null) { + this.hazelcastInstance.getCluster().removeMembershipListener(id); + } + + id = this.hazelcastRegisteredListenerIdMap.remove(ClusterMonitorType.DISTRIBUTED_OBJECT); + if (id != null) { + this.hazelcastInstance.removeDistributedObjectListener(id); + } + + id = this.hazelcastRegisteredListenerIdMap.remove(ClusterMonitorType.MIGRATION); + if (id != null) { + this.hazelcastInstance.getPartitionService().removeMigrationListener(id); + } + + id = this.hazelcastRegisteredListenerIdMap.remove(ClusterMonitorType.LIFECYCLE); + if (id != null) { + this.hazelcastInstance.getLifecycleService().removeLifecycleListener(id); + } + + id = this.hazelcastRegisteredListenerIdMap.remove(ClusterMonitorType.CLIENT); + if (id != null) { + this.hazelcastInstance.getClientService().removeClientListener(id); + } + } + } + + @Override + public String getComponentType() { + return "hazelcast:cm-inbound-channel-adapter"; + } + + private void processEvent(Object event) { + Assert.notNull(event, "'hazelcast event' must not be null"); + if (logger.isDebugEnabled()) { + logger.debug("Received Cluster Monitor Event : " + event); + } + this.sendMessage(getMessageBuilderFactory().withPayload(event).build()); + + } + + private final class HazelcastClusterMonitorListener implements MembershipListener, + DistributedObjectListener, MigrationListener, LifecycleListener, + ClientListener { + + @Override + public void memberAdded(MembershipEvent membershipEvent) { + processEvent(membershipEvent); + } + + @Override + public void memberRemoved(MembershipEvent membershipEvent) { + processEvent(membershipEvent); + } + + @Override + public void distributedObjectCreated(DistributedObjectEvent event) { + processEvent(event); + } + + @Override + public void distributedObjectDestroyed(DistributedObjectEvent event) { + processEvent(event); + } + + @Override + public void migrationStarted(MigrationState migrationEvent) { + processEvent(migrationEvent); + } + + @Override + public void migrationFinished(MigrationState migrationEvent) { + processEvent(migrationEvent); + } + + @Override + public void replicaMigrationCompleted(ReplicaMigrationEvent event) { + processEvent(event); + } + + @Override + public void replicaMigrationFailed(ReplicaMigrationEvent event) { + processEvent(event); + } + + @Override + public void stateChanged(LifecycleEvent event) { + processEvent(event); + } + + @Override + public void clientConnected(Client client) { + processEvent(client); + } + + @Override + public void clientDisconnected(Client client) { + processEvent(client); + } + + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastContinuousQueryMessageProducer.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastContinuousQueryMessageProducer.java new file mode 100644 index 0000000000..19a03bf303 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastContinuousQueryMessageProducer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import org.springframework.util.Assert; + +import com.hazelcast.map.IMap; +import com.hazelcast.query.impl.predicates.SqlPredicate; + +/** + * Hazelcast Continuous Query Message Producer is a message producer which enables + * {@link AbstractHazelcastMessageProducer.HazelcastEntryListener} with a + * {@link SqlPredicate} in order to listen related distributed map events in the light of + * defined predicate and sends events to related channel. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastContinuousQueryMessageProducer extends AbstractHazelcastMessageProducer { + + private final String predicate; + + private boolean includeValue = true; + + @SuppressWarnings("rawtypes") + public HazelcastContinuousQueryMessageProducer(IMap distributedMap, String predicate) { + super(distributedMap); + Assert.hasText(predicate, "'predicate' must not be null"); + this.predicate = predicate; + } + + public void setIncludeValue(boolean includeValue) { + this.includeValue = includeValue; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected void doStart() { + setHazelcastRegisteredEventListenerId(((IMap) this.distributedObject) + .addEntryListener(new HazelcastEntryListener(), new SqlPredicate(this.predicate), + this.includeValue)); + } + + @Override + protected void doStop() { + ((IMap) this.distributedObject).removeEntryListener(getHazelcastRegisteredEventListenerId()); + } + + @Override + public String getComponentType() { + return "hazelcast:cq-inbound-channel-adapter"; + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLMessageSource.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLMessageSource.java new file mode 100644 index 0000000000..333ab5ddc8 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLMessageSource.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.integration.endpoint.AbstractMessageSource; +import org.springframework.integration.hazelcast.DistributedSQLIterationType; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import com.hazelcast.map.IMap; +import com.hazelcast.query.impl.predicates.SqlPredicate; + +/** + * Hazelcast Distributed SQL Message Source is a message source which runs defined + * distributed query in the cluster and returns results in the light of iteration type. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@SuppressWarnings("rawtypes") +public class HazelcastDistributedSQLMessageSource extends AbstractMessageSource { + + private final IMap distributedMap; + + private final String distributedSql; + + private DistributedSQLIterationType iterationType = DistributedSQLIterationType.VALUE; + + public HazelcastDistributedSQLMessageSource(IMap distributedMap, String distributedSql) { + Assert.notNull(distributedMap, "'distributedMap' must not be null"); + Assert.hasText(distributedSql, "'distributedSql' must not be empty"); + this.distributedMap = distributedMap; + this.distributedSql = distributedSql; + } + + public void setIterationType(DistributedSQLIterationType iterationType) { + Assert.notNull(this.iterationType, "'iterationType' must not be null"); + this.iterationType = iterationType; + } + + @Override + public String getComponentType() { + return "hazelcast:ds-inbound-channel-adapter"; + } + + @Override + @SuppressWarnings("unchecked") + protected Collection doReceive() { + switch (this.iterationType) { + case ENTRY: + return getDistributedSQLResultSet(Collections + .unmodifiableCollection(this.distributedMap.entrySet(new SqlPredicate(this.distributedSql)))); + + case KEY: + return getDistributedSQLResultSet(Collections + .unmodifiableCollection(this.distributedMap.keySet(new SqlPredicate(this.distributedSql)))); + + case LOCAL_KEY: + return getDistributedSQLResultSet(Collections + .unmodifiableCollection(this.distributedMap.localKeySet(new SqlPredicate(this.distributedSql)))); + + default: + return getDistributedSQLResultSet(this.distributedMap.values(new SqlPredicate(this.distributedSql))); + } + } + + private Collection getDistributedSQLResultSet(Collection collection) { + if (CollectionUtils.isEmpty(collection)) { + return null; + } + + return collection; + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastEventDrivenMessageProducer.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastEventDrivenMessageProducer.java new file mode 100644 index 0000000000..491c747280 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/HazelcastEventDrivenMessageProducer.java @@ -0,0 +1,197 @@ +/* + * Copyright 2015-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationDefinitionValidator; +import org.springframework.util.Assert; + +import com.hazelcast.collection.IList; +import com.hazelcast.collection.IQueue; +import com.hazelcast.collection.ISet; +import com.hazelcast.collection.ItemEvent; +import com.hazelcast.collection.ItemListener; +import com.hazelcast.core.DistributedObject; +import com.hazelcast.instance.EndpointQualifier; +import com.hazelcast.map.IMap; +import com.hazelcast.map.listener.MapListener; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; +import com.hazelcast.topic.Message; +import com.hazelcast.topic.MessageListener; + +/** + * Hazelcast Event Driven Message Producer is a message producer which enables + * {@link AbstractHazelcastMessageProducer.HazelcastEntryListener}, + * {@link HazelcastEventDrivenMessageProducer.HazelcastItemListener} and + * {@link HazelcastEventDrivenMessageProducer.HazelcastMessageListener} listeners in order + * to listen related cache events and sends events to related channel. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastEventDrivenMessageProducer extends AbstractHazelcastMessageProducer { + + public HazelcastEventDrivenMessageProducer(DistributedObject distributedObject) { + super(distributedObject); + } + + @Override + protected void onInit() { + super.onInit(); + HazelcastIntegrationDefinitionValidator.validateCacheTypeForEventDrivenMessageProducer(this.distributedObject); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected void doStart() { + if (this.distributedObject instanceof IMap) { + setHazelcastRegisteredEventListenerId(((IMap) this.distributedObject) + .addEntryListener((MapListener) new HazelcastEntryListener(), true)); + } + else if (this.distributedObject instanceof MultiMap) { + setHazelcastRegisteredEventListenerId(((MultiMap) this.distributedObject) + .addEntryListener(new HazelcastEntryListener(), true)); + } + else if (this.distributedObject instanceof ReplicatedMap) { + setHazelcastRegisteredEventListenerId(((ReplicatedMap) this.distributedObject) + .addEntryListener(new HazelcastEntryListener())); + } + else if (this.distributedObject instanceof IList) { + setHazelcastRegisteredEventListenerId(((IList) this.distributedObject) + .addItemListener(new HazelcastItemListener(), true)); + } + else if (this.distributedObject instanceof ISet) { + setHazelcastRegisteredEventListenerId(((ISet) this.distributedObject) + .addItemListener(new HazelcastItemListener(), true)); + } + else if (this.distributedObject instanceof IQueue) { + setHazelcastRegisteredEventListenerId(((IQueue) this.distributedObject) + .addItemListener(new HazelcastItemListener(), true)); + } + else if (this.distributedObject instanceof ITopic) { + setHazelcastRegisteredEventListenerId(((ITopic) this.distributedObject) + .addMessageListener(new HazelcastMessageListener())); + } + } + + @Override + protected void doStop() { + if (this.distributedObject instanceof IMap) { + ((IMap) this.distributedObject).removeEntryListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof MultiMap) { + ((MultiMap) this.distributedObject).removeEntryListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof ReplicatedMap) { + ((ReplicatedMap) this.distributedObject).removeEntryListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof IList) { + ((IList) this.distributedObject).removeItemListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof ISet) { + ((ISet) this.distributedObject).removeItemListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof IQueue) { + ((IQueue) this.distributedObject).removeItemListener(getHazelcastRegisteredEventListenerId()); + } + else if (this.distributedObject instanceof ITopic) { + ((ITopic) this.distributedObject).removeMessageListener(getHazelcastRegisteredEventListenerId()); + } + } + + @Override + public String getComponentType() { + return "hazelcast:inbound-channel-adapter"; + } + + private class HazelcastItemListener extends AbstractHazelcastEventListener> + implements ItemListener { + + @Override + public void itemAdded(ItemEvent item) { + processEvent(item); + } + + @Override + public void itemRemoved(ItemEvent item) { + processEvent(item); + } + + @Override + protected void processEvent(ItemEvent event) { + if (getCacheEvents().contains(event.getEventType().toString())) { + sendMessage(event, + event.getMember().getSocketAddress(EndpointQualifier.MEMBER), getCacheListeningPolicy()); + } + + if (logger.isDebugEnabled()) { + logger.debug("Received ItemEvent : " + event); + } + } + + @Override + protected org.springframework.messaging.Message toMessage(ItemEvent event) { + final Map headers = new HashMap<>(); + headers.put(HazelcastHeaders.EVENT_TYPE, event.getEventType().name()); + headers.put(HazelcastHeaders.MEMBER, event.getMember().getSocketAddress(EndpointQualifier.MEMBER)); + + return getMessageBuilderFactory().withPayload(event.getItem()).copyHeaders(headers).build(); + } + + } + + private class HazelcastMessageListener extends AbstractHazelcastEventListener> + implements MessageListener { + + @Override + public void onMessage(Message message) { + processEvent(message); + } + + @Override + protected void processEvent(Message event) { + sendMessage(event, + event.getPublishingMember().getSocketAddress(EndpointQualifier.MEMBER), getCacheListeningPolicy()); + + if (logger.isDebugEnabled()) { + logger.debug("Received Message : " + event); + } + } + + @Override + protected org.springframework.messaging.Message toMessage(Message event) { + Assert.notNull(event.getMessageObject(), "message must not be null"); + + final Map headers = new HashMap<>(); + headers.put(HazelcastHeaders.MEMBER, + event.getPublishingMember().getSocketAddress(EndpointQualifier.MEMBER)); + headers.put(HazelcastHeaders.CACHE_NAME, event.getSource()); + headers.put(HazelcastHeaders.PUBLISHING_TIME, event.getPublishTime()); + + return getMessageBuilderFactory().withPayload(event.getMessageObject()).copyHeaders(headers).build(); + } + + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/package-info.java new file mode 100644 index 0000000000..da7cff95db --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes supporting inbound endpoints. + */ +package org.springframework.integration.hazelcast.inbound; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/LeaderInitiator.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/LeaderInitiator.java new file mode 100644 index 0000000000..41cb1238a9 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/LeaderInitiator.java @@ -0,0 +1,429 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.leader; + + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.Context; +import org.springframework.integration.leader.DefaultCandidate; +import org.springframework.integration.leader.event.DefaultLeaderEventPublisher; +import org.springframework.integration.leader.event.LeaderEventPublisher; +import org.springframework.integration.support.leader.LockRegistryLeaderInitiator; +import org.springframework.util.Assert; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.cp.CPSubsystem; +import com.hazelcast.cp.lock.FencedLock; + +/** + * Bootstrap leadership {@link org.springframework.integration.leader.Candidate candidates} + * with Hazelcast. Upon construction, {@link #start} must be invoked to + * register the candidate for leadership election. + * + * @author Patrick Peralta + * @author Gary Russell + * @author Dave Syer + * @author Artem Bilan + * @author Mael Le Guével + * @author Alexey Tsoy + * @author Robert Höglund + */ +public class LeaderInitiator implements SmartLifecycle, DisposableBean, ApplicationEventPublisherAware { + + private static final Log logger = LogFactory.getLog(LeaderInitiator.class); + + private static int threadNameCount = 0; + + private static final Context NULL_CONTEXT = new NullContext(); + + /*** Hazelcast client. + */ + private final HazelcastInstance client; + + /** + * Candidate for leader election. + */ + private final Candidate candidate; + + /** + * Executor service for running leadership daemon. + */ + private final ExecutorService executorService = + Executors.newSingleThreadExecutor(r -> { + Thread thread = new Thread(r, "Hazelcast-leadership-" + (threadNameCount++)); + thread.setDaemon(true); + return thread; + }); + + private long heartBeatMillis = LockRegistryLeaderInitiator.DEFAULT_HEART_BEAT_TIME; + + private long busyWaitMillis = LockRegistryLeaderInitiator.DEFAULT_BUSY_WAIT_TIME; + + private LeaderSelector leaderSelector; + + /** + * Leader event publisher. + */ + private LeaderEventPublisher leaderEventPublisher = new DefaultLeaderEventPublisher(); + + private boolean autoStartup = true; + + private int phase; + + /** + * Future returned by submitting an {@link LeaderSelector} to {@link #executorService}. + * This is used to cancel leadership. + */ + private volatile Future future; + + private boolean customPublisher = false; + + private volatile boolean running; + + private final Semaphore yieldSign = new Semaphore(0); + + /** + * Construct a {@link LeaderInitiator} with a default candidate. + * @param client Hazelcast client + */ + public LeaderInitiator(HazelcastInstance client) { + this(client, new DefaultCandidate()); + } + + /** + * Construct a {@link LeaderInitiator}. + * @param client Hazelcast client + * @param candidate leadership election candidate + */ + public LeaderInitiator(HazelcastInstance client, Candidate candidate) { + Assert.notNull(client, "'client' must not be null"); + Assert.notNull(candidate, "'candidate' must not be null"); + this.client = client; + this.candidate = candidate; + } + + /** + * Sets the {@link LeaderEventPublisher}. + * @param leaderEventPublisher the event publisher + */ + public void setLeaderEventPublisher(LeaderEventPublisher leaderEventPublisher) { + Assert.notNull(leaderEventPublisher, "'leaderEventPublisher' must not be null"); + this.leaderEventPublisher = leaderEventPublisher; + this.customPublisher = true; + } + + /** + * Time in milliseconds to wait in between attempts to re-acquire the lock, once it is + * held. The heartbeat time has to be less than the remote lock expiry period, if + * there is one, otherwise other nodes can steal the lock while we are sleeping here. + * @param heartBeatMillis the heart-beat timeout in milliseconds. + * Defaults to {@link LockRegistryLeaderInitiator#DEFAULT_HEART_BEAT_TIME} + * @since 1.0.1 + */ + public void setHeartBeatMillis(long heartBeatMillis) { + this.heartBeatMillis = heartBeatMillis; + } + + /** + * Time in milliseconds to wait in between attempts to acquire the lock, if it is not + * held. The longer this is, the longer the system can be leaderless, if the leader + * dies. If a leader dies without releasing its lock, the system might still have to + * wait for the old lock to expire, but after that it should not have to wait longer + * than the busy wait time to get a new leader. + * @param busyWaitMillis the busy-wait timeout in milliseconds + * Defaults to {@link LockRegistryLeaderInitiator#DEFAULT_BUSY_WAIT_TIME} + * @since 1.0.1 + */ + public void setBusyWaitMillis(long busyWaitMillis) { + this.busyWaitMillis = busyWaitMillis; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + if (!this.customPublisher) { + this.leaderEventPublisher = new DefaultLeaderEventPublisher(applicationEventPublisher); + } + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setPhase(int phase) { + this.phase = phase; + } + + @Override + public int getPhase() { + return this.phase; + } + + /** + * The context of the initiator or null if not running. + * @return the context (or null if not running) + */ + public Context getContext() { + if (this.leaderSelector == null) { + return NULL_CONTEXT; + } + return this.leaderSelector.context; + } + + /** + * Start the registration of the {@link #candidate} for leader election. + */ + @Override + public synchronized void start() { + if (!this.running) { + this.leaderSelector = new LeaderSelector(); + this.running = true; + this.future = this.executorService.submit(this.leaderSelector); + } + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + /** + * Stop the registration of the {@link #candidate} for leader election. + * If the candidate is currently leader, its leadership will be revoked. + */ + @Override + public synchronized void stop() { + if (this.running) { + this.running = false; + if (this.future != null) { + this.future.cancel(true); + } + this.future = null; + } + } + + /** + * {@code true} if leadership election for this {@link #candidate} is running. + * @return true if leadership election for this {@link #candidate} is running + */ + @Override + public boolean isRunning() { + return this.running; + } + + @Override + public void destroy() { + stop(); + this.executorService.shutdown(); + } + + + FencedLock getLock() { + CPSubsystem cpSubSystem = this.client.getCPSubsystem(); + FencedLock lock = cpSubSystem.getLock(this.candidate.getRole()); + if (logger.isDebugEnabled()) { + logger.debug( + String.format("Use lock groupId '%s', lock count '%s'", lock.getGroupId(), lock.getLockCount())); + } + return lock; + } + + + /** + * Callable that manages the acquisition of Hazelcast locks + * for leadership election. + */ + protected class LeaderSelector implements Callable { + + protected final HazelcastContext context = new HazelcastContext(); + + protected final String role = LeaderInitiator.this.candidate.getRole(); + + private volatile boolean leader = false; + + @Override + public Void call() { + try { + while (isRunning()) { + try { + if (logger.isTraceEnabled()) { + logger.trace("Am I the leader (" + LeaderInitiator.this.candidate.getRole() + ") ? " + + this.leader); + } + if (getLock().isLockedByCurrentThread()) { + if (!this.leader) { + // Since we have the lock we need to ensure that the leader flag is set + this.leader = true; + } + // Give it a chance to expire. + if (LeaderInitiator.this.yieldSign.tryAcquire(LeaderInitiator.this.heartBeatMillis, TimeUnit.MILLISECONDS)) { + revokeLeadership(); + // Give it a chance to elect some other leader. + Thread.sleep(LeaderInitiator.this.busyWaitMillis); + } + } + else { + // We try to acquire the lock + boolean acquired = getLock() + .tryLock(LeaderInitiator.this.heartBeatMillis, TimeUnit.MILLISECONDS); + if (acquired && !this.leader) { + // Success: we are now leader + this.leader = true; + handleGranted(); + } + } + } + catch (Exception e) { + // The lock was broken and we are no longer leader + revokeLeadership(); + + + if (isRunning()) { + // Give it a chance to elect some other leader. + try { + Thread.sleep(LeaderInitiator.this.busyWaitMillis); + } + catch (InterruptedException e1) { + // Ignore interruption and let it to be caught on the next cycle. + Thread.currentThread().interrupt(); + } + } + if (logger.isDebugEnabled()) { + logger.debug("Error acquiring the lock for " + this.context + + ". " + (isRunning() ? "Retrying..." : ""), e); + } + } + } + } + finally { + revokeLeadership(); + } + + return null; + } + + private void revokeLeadership() { + if (this.leader) { + this.leader = false; + try { + // Try to unlock + getLock().unlock(); + } + catch (Exception e1) { + logger.warn("Could not unlock - treat as broken " + this.context + ". Revoking " + + (isRunning() ? " and retrying..." : "..."), e1); + + } + + handleRevoked(); + } + } + + + private void handleGranted() throws InterruptedException { + LeaderInitiator.this.candidate.onGranted(this.context); + if (LeaderInitiator.this.leaderEventPublisher != null) { + try { + LeaderInitiator.this.leaderEventPublisher.publishOnGranted( + LeaderInitiator.this, this.context, this.role); + } + catch (Exception e) { + logger.warn("Error publishing OnGranted event.", e); + } + } + } + + private void handleRevoked() { + LeaderInitiator.this.candidate.onRevoked(this.context); + if (LeaderInitiator.this.leaderEventPublisher != null) { + try { + LeaderInitiator.this.leaderEventPublisher.publishOnRevoked( + LeaderInitiator.this, this.context, this.role); + } + catch (Exception e) { + logger.warn("Error publishing OnRevoked event.", e); + } + } + } + + } + + /** + * Implementation of leadership context backed by Hazelcast. + */ + protected class HazelcastContext implements Context { + + @Override + public boolean isLeader() { + return LeaderInitiator.this.leaderSelector.leader; + } + + @Override + public void yield() { + if (isLeader()) { + LeaderInitiator.this.yieldSign.release(); + } + } + + @Override + public String getRole() { + return LeaderInitiator.this.candidate.getRole(); + } + + @Override + public String toString() { + return "HazelcastContext{role=" + LeaderInitiator.this.candidate.getRole() + + ", id=" + LeaderInitiator.this.candidate.getId() + + ", isLeader=" + isLeader() + "}"; + } + + } + + private static final class NullContext implements Context { + + @Override + public boolean isLeader() { + return false; + } + + @Override + public void yield() { + // No-op + } + + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/package-info.java new file mode 100644 index 0000000000..edd7be0bb4 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the Leader Initiator support classes. + */ +package org.springframework.integration.hazelcast.leader; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/HazelcastMembershipListener.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/HazelcastMembershipListener.java new file mode 100644 index 0000000000..faa0bc8c68 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/HazelcastMembershipListener.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.listener; + +import java.net.SocketAddress; +import java.util.Set; +import java.util.concurrent.locks.Lock; + +import org.springframework.integration.hazelcast.HazelcastLocalInstanceRegistrar; + +import com.hazelcast.cluster.MembershipAdapter; +import com.hazelcast.cluster.MembershipEvent; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.instance.EndpointQualifier; +import com.hazelcast.multimap.MultiMap; + +/** + * Hazelcast {@link MembershipAdapter} in order to listen for membership updates in the cluster. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastMembershipListener extends MembershipAdapter { + + @Override + public void memberRemoved(MembershipEvent membershipEvent) { + SocketAddress removedMemberSocketAddress = + membershipEvent.getMember().getSocketAddress(EndpointQualifier.MEMBER); + Set hazelcastLocalInstanceSet = Hazelcast.getAllHazelcastInstances(); + if (!hazelcastLocalInstanceSet.isEmpty()) { + HazelcastInstance hazelcastInstance = hazelcastLocalInstanceSet.iterator().next(); + Lock lock = + hazelcastInstance.getCPSubsystem() + .getLock(HazelcastLocalInstanceRegistrar.SPRING_INTEGRATION_INTERNAL_CLUSTER_LOCK); + lock.lock(); + try { + MultiMap configMultiMap = hazelcastInstance + .getMultiMap(HazelcastLocalInstanceRegistrar.SPRING_INTEGRATION_INTERNAL_CLUSTER_MULTIMAP); + + if (configMultiMap.containsKey(removedMemberSocketAddress)) { + SocketAddress newAdminSocketAddress = getNewAdminInstanceSocketAddress( + configMultiMap, removedMemberSocketAddress); + for (SocketAddress socketAddress : configMultiMap.values()) { + if (!socketAddress.equals(removedMemberSocketAddress)) { + configMultiMap.put(newAdminSocketAddress, socketAddress); + } + } + configMultiMap.remove(removedMemberSocketAddress); + } + else { + configMultiMap.remove(configMultiMap.keySet().iterator().next(), removedMemberSocketAddress); + } + } + finally { + lock.unlock(); + } + } + } + + private SocketAddress getNewAdminInstanceSocketAddress( + MultiMap configMultiMap, SocketAddress removedMemberSocketAddress) { + for (SocketAddress socketAddress : configMultiMap.values()) { + if (!socketAddress.equals(removedMemberSocketAddress)) { + return socketAddress; + } + } + + throw new IllegalStateException("No Active Hazelcast Instance Found."); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/package-info.java new file mode 100644 index 0000000000..e63c2d1297 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/listener/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes for listeners. + */ +package org.springframework.integration.hazelcast.listener; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistry.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistry.java new file mode 100644 index 0000000000..d0109470b9 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistry.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.lock; + +import java.util.concurrent.locks.Lock; + +import org.springframework.integration.support.locks.LockRegistry; +import org.springframework.util.Assert; + +import com.hazelcast.core.HazelcastInstance; + +/** + * A {@link LockRegistry} implementation Hazelcast distributed locks. + * + * @author Artem Bilan + */ +public class HazelcastLockRegistry implements LockRegistry { + + private final HazelcastInstance client; + + public HazelcastLockRegistry(HazelcastInstance hazelcastInstance) { + Assert.notNull(hazelcastInstance, "'hazelcastInstance' must not be null"); + this.client = hazelcastInstance; + } + + @Override + public Lock obtain(Object lockKey) { + Assert.isInstanceOf(String.class, lockKey); + return this.client.getCPSubsystem().getLock((String) lockKey); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/package-info.java new file mode 100644 index 0000000000..fb8cdc4d95 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/lock/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the distributed Locks support classes. + */ +package org.springframework.integration.hazelcast.lock; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/EntryEventMessagePayload.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/EntryEventMessagePayload.java new file mode 100644 index 0000000000..a30f9e6e47 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/EntryEventMessagePayload.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.message; + +import org.springframework.util.Assert; + +/** + * Hazelcast Message Payload for Entry Events. + * + * @param the entry key type + * @param the entry value type + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class EntryEventMessagePayload { + + /** + * The entry key. + */ + public final K key; + + /** + * The entry value. + */ + public final V value; + + /** + * The entry old value if any. + */ + public final V oldValue; + + public EntryEventMessagePayload(final K key, final V value, final V oldValue) { + Assert.notNull(key, "'key' must not be null"); + this.key = key; + this.value = value; + this.oldValue = oldValue; + } + + @Override + public String toString() { + return "EntryEventMessagePayload [key=" + this.key + ", value=" + this.value + ", oldValue=" + this.oldValue + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + EntryEventMessagePayload that = (EntryEventMessagePayload) o; + + return this.key.equals(that.key) && !(this.value != null ? !this.value.equals(that.value) + : that.value != null) && !(this.oldValue != null + ? !this.oldValue.equals(that.oldValue) : that.oldValue != null); + + } + + @Override + public int hashCode() { + int result = this.key.hashCode(); + result = 31 * result + (this.value != null ? this.value.hashCode() : 0); + result = 31 * result + (this.oldValue != null ? this.oldValue.hashCode() : 0); + return result; + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/package-info.java new file mode 100644 index 0000000000..0a659bfb04 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/message/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes supporting Hazelcast message headers and payload. + */ +package org.springframework.integration.hazelcast.message; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStore.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStore.java new file mode 100644 index 0000000000..2b2fe919ab --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStore.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.metadata; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.metadata.ListenableMetadataStore; +import org.springframework.integration.metadata.MetadataStoreListener; +import org.springframework.util.Assert; + +import com.hazelcast.core.EntryEvent; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; +import com.hazelcast.map.listener.EntryUpdatedListener; + +/** + * The Hazelcast {@link IMap}-based {@link ListenableMetadataStore} implementation. + * + * @author Vinicius Carvalho + * @author Artem Bilan + */ +public class HazelcastMetadataStore implements ListenableMetadataStore, InitializingBean { + + private static final String METADATA_STORE_MAP_NAME = "SPRING_INTEGRATION_METADATA_STORE"; + + private final IMap map; + + private final List listeners = new CopyOnWriteArrayList(); + + public HazelcastMetadataStore(HazelcastInstance hazelcastInstance) { + Assert.notNull(hazelcastInstance, "Hazelcast instance can't be null"); + this.map = hazelcastInstance.getMap(METADATA_STORE_MAP_NAME); + } + + public HazelcastMetadataStore(IMap map) { + Assert.notNull(map, "IMap reference can not be null"); + this.map = map; + } + + @Override + public String putIfAbsent(String key, String value) { + Assert.notNull(key, "'key' must not be null."); + Assert.notNull(value, "'value' must not be null."); + return this.map.putIfAbsent(key, value); + } + + @Override + public boolean replace(String key, String oldValue, String newValue) { + Assert.notNull(key, "'key' must not be null."); + Assert.notNull(oldValue, "'oldValue' must not be null."); + Assert.notNull(newValue, "'newValue' must not be null."); + return this.map.replace(key, oldValue, newValue); + } + + @Override + public void put(String key, String value) { + Assert.notNull(key, "'key' must not be null."); + Assert.notNull(value, "'value' must not be null."); + this.map.put(key, value); + } + + @Override + public String get(String key) { + Assert.notNull(key, "'key' must not be null."); + return this.map.get(key); + } + + @Override + public String remove(String key) { + Assert.notNull(key, "'key' must not be null."); + return this.map.remove(key); + } + + @Override + public void addListener(MetadataStoreListener callback) { + Assert.notNull(callback, "callback object can not be null"); + this.listeners.add(callback); + } + + @Override + public void removeListener(MetadataStoreListener callback) { + this.listeners.remove(callback); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.map.addEntryListener(new MapListener(this.listeners), true); + } + + private static class MapListener implements EntryAddedListener, + EntryRemovedListener, EntryUpdatedListener { + + private final List listeners; + + MapListener(List listeners) { + this.listeners = listeners; + } + + @Override + public void entryAdded(EntryEvent event) { + for (MetadataStoreListener listener : this.listeners) { + listener.onAdd(event.getKey(), event.getValue()); + } + } + + @Override + public void entryRemoved(EntryEvent event) { + for (MetadataStoreListener listener : this.listeners) { + listener.onRemove(event.getKey(), event.getOldValue()); + } + } + + @Override + public void entryUpdated(EntryEvent event) { + for (MetadataStoreListener listener : this.listeners) { + listener.onUpdate(event.getKey(), event.getValue()); + } + } + + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/package-info.java new file mode 100644 index 0000000000..1310782726 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/metadata/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the Metadata Store support classes. + */ +package org.springframework.integration.hazelcast.metadata; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/HazelcastCacheWritingMessageHandler.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/HazelcastCacheWritingMessageHandler.java new file mode 100644 index 0000000000..27c7dfc9c6 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/HazelcastCacheWritingMessageHandler.java @@ -0,0 +1,167 @@ +/* + * Copyright 2015-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.outbound; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +import com.hazelcast.core.DistributedObject; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.topic.ITopic; + +/** + * MessageHandler implementation that writes {@link Message} or payload to defined + * Hazelcast distributed cache object. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastCacheWritingMessageHandler extends AbstractMessageHandler { + + private DistributedObject distributedObject; + + private Expression cacheExpression; + + private Expression keyExpression; + + private boolean extractPayload = true; + + private EvaluationContext evaluationContext; + + public void setDistributedObject(DistributedObject distributedObject) { + Assert.notNull(distributedObject, "'distributedObject' must not be null"); + this.distributedObject = distributedObject; + } + + public void setCacheExpression(Expression cacheExpression) { + Assert.notNull(cacheExpression, "'cacheExpression' must not be null"); + this.cacheExpression = cacheExpression; + } + + public void setKeyExpression(Expression keyExpression) { + Assert.notNull(keyExpression, "'keyExpression' must not be null"); + this.keyExpression = keyExpression; + } + + public void setExtractPayload(boolean extractPayload) { + this.extractPayload = extractPayload; + } + + @Override + protected void onInit() { + super.onInit(); + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + protected void handleMessageInternal(final Message message) { + Object objectToStore = message; + if (this.extractPayload) { + objectToStore = message.getPayload(); + } + + DistributedObject distributedObject = getDistributedObject(message); + + if (distributedObject instanceof Map) { + Map map = (Map) distributedObject; + if (objectToStore instanceof Map) { + map.putAll((Map) objectToStore); + } + else if (objectToStore instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) objectToStore; + map.put(entry.getKey(), entry.getValue()); + } + else { + map.put(getKey(message), objectToStore); + } + } + else if (distributedObject instanceof MultiMap) { + MultiMap map = (MultiMap) distributedObject; + if (objectToStore instanceof Map) { + Map mapToStore = (Map) objectToStore; + for (Map.Entry entry : mapToStore.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + } + else if (objectToStore instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) objectToStore; + map.put(entry.getKey(), entry.getValue()); + } + else { + map.put(getKey(message), objectToStore); + } + } + else if (distributedObject instanceof ITopic) { + ((ITopic) distributedObject).publish(objectToStore); + } + else if (distributedObject instanceof Collection) { + if (objectToStore instanceof Collection) { + ((Collection) distributedObject).addAll((Collection) objectToStore); + } + else { + ((Collection) distributedObject).add(objectToStore); + } + } + else { + throw new IllegalStateException("The 'distributedObject' for 'HazelcastCacheWritingMessageHandler' " + + "must be of 'IMap', 'MultiMap', 'ITopic', 'ISet' or 'IList' type, " + + "but gotten: [" + distributedObject + "]."); + } + } + + + private DistributedObject getDistributedObject(final Message message) { + if (this.distributedObject != null) { + return this.distributedObject; + } + else if (this.cacheExpression != null) { + return this.cacheExpression.getValue(this.evaluationContext, message, DistributedObject.class); + } + else if (message.getHeaders().containsKey(HazelcastHeaders.CACHE_NAME)) { + return getBeanFactory() + .getBean(message.getHeaders().get(HazelcastHeaders.CACHE_NAME, String.class), + DistributedObject.class); + } + else { + throw new IllegalStateException("One of 'cache', 'cache-expression' and " + + HazelcastHeaders.CACHE_NAME + + " must be set for cache object definition."); + } + } + + private Object getKey(Message message) { + if (this.keyExpression != null) { + return this.keyExpression.getValue(this.evaluationContext, message); + } + else { + throw new IllegalStateException( + "'key-expression' must be set to place the raw 'payload' to the IMap, MultiMap and ReplicatedMap"); + } + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/package-info.java new file mode 100644 index 0000000000..08f03f96db --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes supporting outbound endpoints. + */ +package org.springframework.integration.hazelcast.outbound; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/package-info.java new file mode 100644 index 0000000000..cdf25e314f --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides common used types and classes. + */ +package org.springframework.integration.hazelcast; diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/HazelcastMessageStore.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/HazelcastMessageStore.java new file mode 100644 index 0000000000..2ff361d2e1 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/HazelcastMessageStore.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.store; + +import java.util.Collection; + +import org.springframework.integration.store.AbstractKeyValueMessageStore; +import org.springframework.util.Assert; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import com.hazelcast.query.Predicates; +import com.hazelcast.query.QueryConstants; + +/** + * The Hazelcast {@link IMap}-based {@link AbstractKeyValueMessageStore} implementation. + * + * @author Vinicius Carvalho + * @author Artem Bilan + */ +public class HazelcastMessageStore extends AbstractKeyValueMessageStore { + + private static final String MESSAGE_STORE_MAP_NAME = "SPRING_INTEGRATION_MESSAGE_STORE"; + + private final IMap map; + + public HazelcastMessageStore(HazelcastInstance hazelcastInstance) { + Assert.notNull(hazelcastInstance, "Hazelcast instance can't be null"); + this.map = hazelcastInstance.getMap(MESSAGE_STORE_MAP_NAME); + } + + public HazelcastMessageStore(IMap map) { + Assert.notNull(map, "IMap reference can not be null"); + this.map = map; + } + + @Override + protected Object doRetrieve(Object id) { + return this.map.get(id); + } + + @Override + protected void doStore(Object id, Object objectToStore) { + this.map.put(id, objectToStore); + } + + @Override + protected void doStoreIfAbsent(Object id, Object objectToStore) { + this.map.putIfAbsent(id, objectToStore); + } + + @Override + protected void doRemoveAll(Collection ids) { + this.map.removeAll((mapEntry) -> ids.contains(mapEntry.getKey())); + } + + @Override + protected Object doRemove(Object id) { + return this.map.remove(id); + } + + @Override + protected Collection doListKeys(String keyPattern) { + Assert.hasText(keyPattern, "'keyPattern' must not be empty"); + keyPattern = keyPattern.replaceAll("\\*", "%"); + return this.map.keySet(Predicates.like(QueryConstants.KEY_ATTRIBUTE_NAME.value(), keyPattern)); + } + +} diff --git a/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/package-info.java b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/package-info.java new file mode 100644 index 0000000000..4657766f35 --- /dev/null +++ b/spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/store/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the Message Store support classes. + */ +package org.springframework.integration.hazelcast.store; diff --git a/spring-integration-hazelcast/src/main/resources/META-INF/spring.factories b/spring-integration-hazelcast/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..d1db7a3bdb --- /dev/null +++ b/spring-integration-hazelcast/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.integration.config.IntegrationConfigurationInitializer=\ +org.springframework.integration.hazelcast.config.HazelcastIntegrationConfigurationInitializer diff --git a/spring-integration-hazelcast/src/main/resources/META-INF/spring.handlers b/spring-integration-hazelcast/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000000..1776823c6f --- /dev/null +++ b/spring-integration-hazelcast/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/hazelcast=org.springframework.integration.hazelcast.config.xml.HazelcastIntegrationNamespaceHandler diff --git a/spring-integration-hazelcast/src/main/resources/META-INF/spring.schemas b/spring-integration-hazelcast/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000000..b198a418fe --- /dev/null +++ b/spring-integration-hazelcast/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,4 @@ +http\://www.springframework.org/schema/integration/hazelcast/spring-integration-hazelcast-1.0.xsd=org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd +http\://www.springframework.org/schema/integration/hazelcast/spring-integration-hazelcast.xsd=org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd +https\://www.springframework.org/schema/integration/hazelcast/spring-integration-hazelcast-1.0.xsd=org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd +https\://www.springframework.org/schema/integration/hazelcast/spring-integration-hazelcast.xsd=org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd diff --git a/spring-integration-hazelcast/src/main/resources/org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd b/spring-integration-hazelcast/src/main/resources/org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd new file mode 100644 index 0000000000..e80d059cea --- /dev/null +++ b/spring-integration-hazelcast/src/main/resources/org/springframework/integration/hazelcast/config/xml/spring-integration-hazelcast.xsd @@ -0,0 +1,303 @@ + + + + + + + + + + + + Configures Hazelcast Event-Driven Inbound Channel Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configures Hazelcast Outbound Channel Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configures Hazelcast Continuous Query Inbound Channel Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configures Hazelcast Distributed SQL Inbound Channel Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configures Hazelcast Cluster Monitor Inbound Channel Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests-context.xml new file mode 100644 index 0000000000..b731b11b51 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests-context.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests.java new file mode 100644 index 0000000000..35ede89bf9 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationDefinitionValidatorTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.collection.IList; +import com.hazelcast.core.DistributedObject; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Hazelcast Integration Definition Validator Test Class + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "rawtypes" }) +public class HazelcastIntegrationDefinitionValidatorTests { + + @Autowired + private IList distList; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testValidateEnumType() { + final String cacheEventTypes = + " ADDED, REMOVED, UPDATED, EVICTED, EVICT_ALL, CLEAR_ALL "; + final Set typeSet = HazelcastIntegrationDefinitionValidator + .validateEnumType(CacheEventType.class, cacheEventTypes); + assertThat(typeSet.size()).isEqualTo(6); + for (String type : typeSet) { + Enum.valueOf(CacheEventType.class, type); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testValidateEnumTypeWithInvalidValue() { + final String cacheEventTypes = "Invalid_Enum_Type"; + HazelcastIntegrationDefinitionValidator + .validateEnumType(CacheEventType.class, cacheEventTypes); + } + + @Test + public void testValidateCacheEventsByDistributedObject() { + Set cacheEventTypeSet = new HashSet<>(2); + cacheEventTypeSet.add(CacheEventType.ADDED.toString()); + cacheEventTypeSet.add(CacheEventType.REMOVED.toString()); + HazelcastIntegrationDefinitionValidator + .validateCacheEventsByDistributedObject(this.distList, cacheEventTypeSet); + } + + @Test(expected = IllegalArgumentException.class) + public void testValidateCacheEventsByDistributedObjectWithInvalidValue() { + Set cacheEventTypeSet = new HashSet<>(1); + cacheEventTypeSet.add("Invalid_Cache_Event_Type"); + HazelcastIntegrationDefinitionValidator + .validateCacheEventsByDistributedObject(this.distList, cacheEventTypeSet); + } + + @Test + public void testValidateCacheTypeForEventDrivenMessageProducer() { + HazelcastIntegrationDefinitionValidator + .validateCacheTypeForEventDrivenMessageProducer(this.distList); + } + + @Test(expected = IllegalArgumentException.class) + public void testValidateCacheTypeForEventDrivenMessageProducerWithUnexpectedDistObject() { + HazelcastIntegrationDefinitionValidator + .validateCacheTypeForEventDrivenMessageProducer(new DistributedObject() { + + @Override + public String getPartitionKey() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public String getServiceName() { + return null; + } + + @Override + public void destroy() { + + } + + }); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationTestUser.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationTestUser.java new file mode 100644 index 0000000000..7c908f8b08 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastIntegrationTestUser.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +import java.io.Serializable; +import java.util.Objects; + +/** + * User Bean for Hazelcast Integration Unit Tests + * + * @author Eren Avsarogullari + * @since 6.0 + */ +public class HazelcastIntegrationTestUser implements Comparable, Serializable { + + private static final long serialVersionUID = -5357485957528362705L; + + private int id; + + private String name; + + private String surname; + + private int age; + + public HazelcastIntegrationTestUser(int id, String name, String surname) { + this.id = id; + this.name = name; + this.surname = surname; + } + + public HazelcastIntegrationTestUser(int id, String name, String surname, int age) { + this.id = id; + this.name = name; + this.surname = surname; + this.age = age; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", surname=" + surname + ", age=" + + age + "]"; + } + + @Override + public int compareTo(HazelcastIntegrationTestUser user) { + return (this.id < user.getId()) ? -1 : (this.id > user.getId()) ? 1 : 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HazelcastIntegrationTestUser that = (HazelcastIntegrationTestUser) o; + return id == that.id && + age == that.age && + Objects.equals(name, that.name) && + Objects.equals(surname, that.surname); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, surname, age); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastTestRequestHandlerAdvice.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastTestRequestHandlerAdvice.java new file mode 100644 index 0000000000..fc6ad49b70 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/HazelcastTestRequestHandlerAdvice.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.messaging.Message; + +/** + * {@link AbstractRequestHandlerAdvice} advice class for Hazelcast Integration Unit Tests. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +public class HazelcastTestRequestHandlerAdvice extends AbstractRequestHandlerAdvice { + + public CountDownLatch executeLatch = null; + + public HazelcastTestRequestHandlerAdvice(int count) { + this.executeLatch = new CountDownLatch(count); + } + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) { + try { + return callback.execute(); + } + finally { + this.executeLatch.countDown(); + } + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/common-config.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/common-config.xml new file mode 100644 index 0000000000..fe00317838 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/common-config.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..0ff55f49ea --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests-context.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests.java new file mode 100644 index 0000000000..19cb8d98e9 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastCQDistributedMapInboundChannelAdapterTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; + +/** + * Hazelcast Continuous Query Inbound Channel Adapter Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastCQDistributedMapInboundChannelAdapterTests { + + @Autowired + private PollableChannel cqMapChannel1; + + @Autowired + private PollableChannel cqMapChannel2; + + @Autowired + private PollableChannel cqMapChannel3; + + @Autowired + private PollableChannel cqMapChannel4; + + @Autowired + private PollableChannel cqMapChannel5; + + @Autowired + private IMap cqDistributedMap1; + + @Autowired + private IMap cqDistributedMap2; + + @Autowired + private IMap cqDistributedMap3; + + @Autowired + private IMap cqDistributedMap4; + + @Autowired + private IMap cqDistributedMap5; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testContinuousQueryForOnlyADDEDEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForADDEDDistributedMapEntryEvent(cqDistributedMap1, + cqMapChannel1, "cqDistributedMap1"); + } + + @Test + public void testContinuousQueryForOnlyREMOVEDEntryEvent() { + cqDistributedMap2 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + cqDistributedMap2 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + cqDistributedMap2.remove(2); + Message msg = + cqMapChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.REMOVED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("cqDistributedMap2"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testContinuousQueryForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedMapEntryEvents(cqDistributedMap3, + cqMapChannel3, "cqDistributedMap3"); + } + + @Test + public void testContinuousQueryForOnlyUPDATEDEntryEvent() { + cqDistributedMap4 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + cqDistributedMap4 + .put(1, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + Message msg = + cqMapChannel4.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.UPDATED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("cqDistributedMap4"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(1)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testContinuousQueryForOnlyUPDATEDEntryEventWhenIncludeValueIsFalse() { + HazelcastInboundChannelAdapterTestUtils + .testContinuousQueryForUPDATEDEntryEventWhenIncludeValueIsFalse( + cqDistributedMap5, cqMapChannel5, "cqDistributedMap5"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..8494afe00b --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests-context.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests.java new file mode 100644 index 0000000000..bc6b3b87e2 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastClusterMonitorInboundChannelAdapterTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.LifecycleEvent; +import com.hazelcast.core.LifecycleEvent.LifecycleState; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Hazelcast Cluster Monitor Inbound Channel Adapter Unit Test Class + * + * @author Eren Avsarogullari + * @author Artem Bilan + */ +@RunWith(SpringRunner.class) +@SpringJUnitConfig +@DirtiesContext +@Ignore("Hard to reach CP consensus with limited number of members in two clusters") +public class HazelcastClusterMonitorInboundChannelAdapterTests { + + private static final String TEST_GROUP_NAME1 = "Test_Group_Name1"; + + @Autowired + private PollableChannel cmChannel1; + + @Autowired + private PollableChannel cmChannel2; + + @Autowired + private PollableChannel cmChannel3; + + @Autowired + private PollableChannel cmChannel4; + + @Autowired + private PollableChannel cmChannel5; + + @Autowired + private PollableChannel cmChannel6; + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Autowired + private HazelcastInstance hazelcastInstance2; + + @Autowired + private HazelcastInstance hazelcastInstance3; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testDistributedObjectEvent() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedObjectEventByChannelAndHazelcastInstance(cmChannel2, + hazelcastInstance, "Test_Distributed_Map4"); + } + + @Test + public void testLifecycleEvent() { + hazelcastInstance2.getLifecycleService().terminate(); + + Message msg = + cmChannel4.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + verifyLifecycleEvent(msg, LifecycleState.SHUTTING_DOWN); + + msg = cmChannel4.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + verifyLifecycleEvent(msg, LifecycleState.SHUTDOWN); + } + + private void verifyLifecycleEvent(final Message msg, + final LifecycleState lifecycleState) { + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof LifecycleEvent).isTrue(); + assertThat(((LifecycleEvent) msg.getPayload()).getState()).isEqualTo(lifecycleState); + } + + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..23e52ac707 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..da756c846f --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedListEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.collection.IList; +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Hazelcast Distributed List Event Driven Inbound Channel Adapter Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedListEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edListChannel1; + + @Autowired + private PollableChannel edListChannel2; + + @Autowired + private PollableChannel edListChannel3; + + @Autowired + private IList edDistributedList1; + + @Autowired + private IList edDistributedList2; + + @Autowired + private IList edDistributedList3; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + edDistributedList1.add(new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = edListChannel1.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.ADDED.toString()); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getId()).isEqualTo(1); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getName()).isEqualTo("TestName1"); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getSurname()).isEqualTo("TestSurname1"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + HazelcastIntegrationTestUser user = new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2"); + edDistributedList2.add(user); + edDistributedList2.remove(user); + Message msg = edListChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.REMOVED.toString()); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getId()).isEqualTo(2); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getName()).isEqualTo("TestName2"); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(edDistributedList3, edListChannel3); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..1664db3047 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..d4dcd0bb2f --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedMapEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,152 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; + +/** + * Hazelcast Distributed Map Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedMapEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edMapChannel1; + + @Autowired + private PollableChannel edMapChannel2; + + @Autowired + private PollableChannel edMapChannel3; + + @Autowired + private PollableChannel edMapChannel4; + + @Autowired + private IMap edDistributedMap1; + + @Autowired + private IMap edDistributedMap2; + + @Autowired + private IMap edDistributedMap3; + + @Autowired + private IMap edDistributedMap4; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForADDEDDistributedMapEntryEvent(edDistributedMap1, + edMapChannel1, "edDistributedMap1"); + } + + @Test + public void testEventDrivenForOnlyUPDATEDEntryEvent() { + edDistributedMap2 + .put(2, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + edDistributedMap2 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + Message msg = + edMapChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.UPDATED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edDistributedMap2"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + edDistributedMap3 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + edDistributedMap3 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + edDistributedMap3.remove(2); + Message msg = + edMapChannel3.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.REMOVED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edDistributedMap3"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedMapEntryEvents(edDistributedMap4, edMapChannel4, + "edDistributedMap4"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..315f221482 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..42b00679a9 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.collection.IQueue; +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Hazelcast Distributed Queue Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedQueueEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edQueueChannel1; + + @Autowired + private PollableChannel edQueueChannel2; + + @Autowired + private PollableChannel edQueueChannel3; + + @Autowired + private IQueue edDistributedQueue1; + + @Autowired + private IQueue edDistributedQueue2; + + @Autowired + private IQueue edDistributedQueue3; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + edDistributedQueue1 + .add(new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = + edQueueChannel1.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.ADDED.toString()); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getId())).isEqualTo(1); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getName())).isEqualTo("TestName1"); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getSurname())).isEqualTo("TestSurname1"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + HazelcastIntegrationTestUser user = + new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2"); + edDistributedQueue2.add(user); + edDistributedQueue2.remove(user); + Message msg = + edQueueChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.REMOVED.toString()); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getId())).isEqualTo(2); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getName())).isEqualTo("TestName2"); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getSurname())).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(edDistributedQueue3, + edQueueChannel3); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..9eeef6df64 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests-context.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests.java new file mode 100644 index 0000000000..f6193c677e --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSQLInboundChannelAdapterTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; + +/** + * Hazelcast Distributed SQL Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedSQLInboundChannelAdapterTests { + + @Autowired + private PollableChannel dsMapChannel1; + + @Autowired + private PollableChannel dsMapChannel2; + + @Autowired + private PollableChannel dsMapChannel3; + + @Autowired + private PollableChannel dsMapChannel4; + + @Autowired + private IMap dsDistributedMap1; + + @Autowired + private IMap dsDistributedMap2; + + @Autowired + private IMap dsDistributedMap3; + + @Autowired + private IMap dsDistributedMap4; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testDistributedSQLForOnlyENTRYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForENTRYIterationType(dsDistributedMap1, dsMapChannel1); + } + + @Test + public void testDistributedSQLForOnlyKEYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForKEYIterationType(dsDistributedMap2, dsMapChannel2); + } + + @Test + public void testDistributedSQLForOnlyLOCAL_KEYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForLOCAL_KEYIterationType(dsDistributedMap3, dsMapChannel3); + } + + @Test + public void testDistributedSQLForOnlyVALUEIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForVALUEIterationType(dsDistributedMap4, dsMapChannel4); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..416b1f29a5 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..c01e159f2a --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedSetEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.collection.ISet; +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Hazelcast Distributed Set Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedSetEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edSetChannel1; + + @Autowired + private PollableChannel edSetChannel2; + + @Autowired + private PollableChannel edSetChannel3; + + @Autowired + private ISet edDistributedSet1; + + @Autowired + private ISet edDistributedSet2; + + @Autowired + private ISet edDistributedSet3; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + edDistributedSet1 + .add(new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = + edSetChannel1.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.ADDED.toString()); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getId())).isEqualTo(1); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getName())).isEqualTo("TestName1"); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getSurname())).isEqualTo("TestSurname1"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + HazelcastIntegrationTestUser user = + new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2"); + edDistributedSet2.add(user); + edDistributedSet2.remove(user); + Message msg = + edSetChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(EntryEventType.REMOVED.toString()); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getId())).isEqualTo(2); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getName())).isEqualTo("TestName2"); + assertThat((((HazelcastIntegrationTestUser) msg.getPayload()).getSurname())).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(edDistributedSet3, + edSetChannel3); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..655d4fce5d --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..d8801f7832 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.topic.ITopic; + +/** + * Hazelcast Distributed Topic Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastDistributedTopicEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edTopicChannel1; + + @Autowired + private ITopic edDistributedTopic1; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForTopicMessageEvent(edDistributedTopic1, edTopicChannel1); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..90fbb6b13a --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..61e23b6623 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastMultiMapEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.multimap.MultiMap; + +/** + * Hazelcast MultiMap Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @author Artem Bilan + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastMultiMapEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edMultiMapChannel1; + + @Autowired + private PollableChannel edMultiMapChannel2; + + @Autowired + private PollableChannel edMultiMapChannel3; + + @Autowired + private MultiMap edMultiMap1; + + @Autowired + private MultiMap edMultiMap2; + + @Autowired + private MultiMap edMultiMap3; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + edMultiMap1 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = + edMultiMapChannel1.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.ADDED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edMultiMap1"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(1)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname1"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + edMultiMap2 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + edMultiMap2 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + edMultiMap2.remove(2); + Message msg = + edMultiMapChannel2.receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.REMOVED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edMultiMap2"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat(((EntryEventMessagePayload) msg.getPayload()).value).isNull(); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForMultiMapEntryEvents(edMultiMap3, edMultiMapChannel3, + "edMultiMap3"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..4e7163f717 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests-context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests.java new file mode 100644 index 0000000000..b2f2c44ebc --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.core.EntryEventType; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.replicatedmap.ReplicatedMap; + +/** + * Hazelcast Replicated Map Event Driven Inbound Channel Adapter Test + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastReplicatedMapEventDrivenInboundChannelAdapterTests { + + @Autowired + private PollableChannel edReplicatedMapChannel1; + + @Autowired + private PollableChannel edReplicatedMapChannel2; + + @Autowired + private PollableChannel edReplicatedMapChannel3; + + @Autowired + private PollableChannel edReplicatedMapChannel4; + + @Autowired + private ReplicatedMap edReplicatedMap1; + + @Autowired + private ReplicatedMap edReplicatedMap2; + + @Autowired + private ReplicatedMap edReplicatedMap3; + + @Autowired + private ReplicatedMap edReplicatedMap4; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testEventDrivenForOnlyADDEDEntryEvent() { + edReplicatedMap1 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = edReplicatedMapChannel1 + .receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.ADDED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edReplicatedMap1"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(1)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname1"); + } + + @Test + public void testEventDrivenForOnlyUPDATEDEntryEvent() { + edReplicatedMap2 + .put(2, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + edReplicatedMap2 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + Message msg = edReplicatedMapChannel2 + .receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.UPDATED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edReplicatedMap2"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForOnlyREMOVEDEntryEvent() { + edReplicatedMap3 + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + edReplicatedMap3 + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + edReplicatedMap3.remove(2); + Message msg = edReplicatedMapChannel3 + .receive(HazelcastInboundChannelAdapterTestUtils.TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.REMOVED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo("edReplicatedMap3"); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(2)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getId()).isEqualTo(2); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getName()).isEqualTo("TestName2"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).oldValue).getSurname()).isEqualTo("TestSurname2"); + } + + @Test + public void testEventDrivenForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForReplicatedMapEntryEvents(edReplicatedMap4, + edReplicatedMapChannel4, "edReplicatedMap4"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastCQDistributedMapInboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastCQDistributedMapInboundChannelAdapterConfigTests.java new file mode 100644 index 0000000000..02b97f03a6 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastCQDistributedMapInboundChannelAdapterConfigTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.config; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.hazelcast.map.IMap; + +/** + * Hazelcast Continuous Query Inbound Channel Adapter JavaConfig driven Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = HazelcastIntegrationInboundTestConfiguration.class, + loader = AnnotationConfigContextLoader.class) +@DirtiesContext +public class HazelcastCQDistributedMapInboundChannelAdapterConfigTests { + + @Autowired + private PollableChannel cqDistributedMapChannel1; + + @Autowired + private PollableChannel cqDistributedMapChannel2; + + @Autowired + private PollableChannel cqDistributedMapChannel3; + + @Autowired + private IMap testCQDistributedMap1; + + @Autowired + private IMap testCQDistributedMap2; + + @Autowired + private IMap testCQDistributedMap3; + + @Test + public void testContinuousQueryForADDEDEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForADDEDDistributedMapEntryEvent(testCQDistributedMap1, + cqDistributedMapChannel1, "Test_CQ_Distributed_Map1"); + } + + @Test + public void testContinuousQueryForALLEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedMapEntryEvents(testCQDistributedMap2, + cqDistributedMapChannel2, "Test_CQ_Distributed_Map2"); + } + + @Test + public void testContinuousQueryForUPDATEDEntryEventWhenIncludeValueIsFalse() { + HazelcastInboundChannelAdapterTestUtils + .testContinuousQueryForUPDATEDEntryEventWhenIncludeValueIsFalse( + testCQDistributedMap3, cqDistributedMapChannel3, + "Test_CQ_Distributed_Map3"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastClusterMonitorInboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastClusterMonitorInboundChannelAdapterConfigTests.java new file mode 100644 index 0000000000..113d895ce3 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastClusterMonitorInboundChannelAdapterConfigTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.config; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.hazelcast.core.HazelcastInstance; + +/** + * Hazelcast Cluster Monitor Inbound Channel Adapter JavaConfig driven Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = HazelcastIntegrationInboundTestConfiguration.class) +@DirtiesContext +public class HazelcastClusterMonitorInboundChannelAdapterConfigTests { + + @Autowired + private PollableChannel cmonChannel; + + @Autowired + private PollableChannel cmonChannel2; + + @Autowired + private HazelcastInstance testHazelcastInstance; + + @Test + public void testConfigDrivenDistributedObjectEvent() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedObjectEventByChannelAndHazelcastInstance(cmonChannel2, + testHazelcastInstance, "Test_Distributed_Map3"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastDistributedSQLInboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastDistributedSQLInboundChannelAdapterConfigTests.java new file mode 100644 index 0000000000..42da06857f --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastDistributedSQLInboundChannelAdapterConfigTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.config; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.hazelcast.map.IMap; + +/** + * Hazelcast Distributed SQL Inbound Channel Adapter JavaConfig driven Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = HazelcastIntegrationInboundTestConfiguration.class, + loader = AnnotationConfigContextLoader.class) +@DirtiesContext +public class HazelcastDistributedSQLInboundChannelAdapterConfigTests { + + @Autowired + private PollableChannel dsDistributedMapChannel; + + @Autowired + private PollableChannel dsDistributedMapChannel2; + + @Autowired + private PollableChannel dsDistributedMapChannel3; + + @Autowired + private PollableChannel dsDistributedMapChannel4; + + @Autowired + private IMap testDSDistributedMap; + + @Autowired + private IMap testDSDistributedMap2; + + @Autowired + private IMap testDSDistributedMap3; + + @Autowired + private IMap testDSDistributedMap4; + + @Test + public void testDistributedSQLForENTRYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForENTRYIterationType(testDSDistributedMap, + dsDistributedMapChannel); + } + + @Test + public void testDistributedSQLForKEYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForKEYIterationType(testDSDistributedMap2, + dsDistributedMapChannel2); + } + + @Test + public void testDistributedSQLForLOCAL_KEYIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForLOCAL_KEYIterationType(testDSDistributedMap3, + dsDistributedMapChannel3); + } + + @Test + public void testDistributedSQLForVALUEIterationType() { + HazelcastInboundChannelAdapterTestUtils + .testDistributedSQLForVALUEIterationType(testDSDistributedMap4, + dsDistributedMapChannel4); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastEventDrivenInboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastEventDrivenInboundChannelAdapterConfigTests.java new file mode 100644 index 0000000000..5b3b97e8bb --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastEventDrivenInboundChannelAdapterConfigTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.config; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.inbound.util.HazelcastInboundChannelAdapterTestUtils; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.hazelcast.collection.IList; +import com.hazelcast.collection.IQueue; +import com.hazelcast.collection.ISet; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Hazelcast Event Driven Inbound Channel Adapter JavaConfig driven Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = HazelcastIntegrationInboundTestConfiguration.class, + loader = AnnotationConfigContextLoader.class) +@DirtiesContext +public class HazelcastEventDrivenInboundChannelAdapterConfigTests { + + @Autowired + private PollableChannel distributedMapChannel; + + @Autowired + private PollableChannel distributedMapChannel2; + + @Autowired + private PollableChannel distributedListChannel; + + @Autowired + private PollableChannel distributedSetChannel; + + @Autowired + private PollableChannel distributedQueueChannel; + + @Autowired + private PollableChannel topicChannel; + + @Autowired + private PollableChannel replicatedMapChannel; + + @Autowired + private PollableChannel multiMapChannel; + + @Autowired + private IMap testDistributedMap; + + @Autowired + private IMap testDistributedMap2; + + @Autowired + private IList testDistributedList; + + @Autowired + private ISet testDistributedSet; + + @Autowired + private IQueue testDistributedQueue; + + @Autowired + private ITopic testTopic; + + @Autowired + private ReplicatedMap testReplicatedMap; + + @Autowired + private MultiMap testMultiMap; + + @Test + public void testEventDrivenForADDEDEntryEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForADDEDDistributedMapEntryEvent(testDistributedMap, + distributedMapChannel, "Test_Distributed_Map"); + } + + @Test + public void testEventDrivenForEntryEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedMapEntryEvents(testDistributedMap2, + distributedMapChannel2, "Test_Distributed_Map2"); + } + + @Test + public void testEventDrivenForDistributedListItemEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(testDistributedList, + distributedListChannel); + } + + @Test + public void testEventDrivenForDistributedSetItemEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(testDistributedSet, + distributedSetChannel); + } + + @Test + public void testEventDrivenForDistributedQueueItemEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForDistributedCollectionItemEvents(testDistributedQueue, + distributedQueueChannel); + } + + @Test + public void testEventDrivenForADDEDMessageEvent() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForTopicMessageEvent(testTopic, topicChannel); + } + + @Test + public void testEventDrivenForReplicatedMapEntryEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForReplicatedMapEntryEvents(testReplicatedMap, + replicatedMapChannel, "Test_Replicated_Map"); + } + + @Test + public void testEventDrivenForMultiMapEntryEvents() { + HazelcastInboundChannelAdapterTestUtils + .testEventDrivenForMultiMapEntryEvents(testMultiMap, multiMapChannel, + "Test_Multi_Map"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastIntegrationInboundTestConfiguration.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastIntegrationInboundTestConfiguration.java new file mode 100644 index 0000000000..180dfbb013 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/config/HazelcastIntegrationInboundTestConfiguration.java @@ -0,0 +1,402 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.config; + +import org.junit.AfterClass; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.InboundChannelAdapter; +import org.springframework.integration.annotation.Poller; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.hazelcast.DistributedSQLIterationType; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.HazelcastLocalInstanceRegistrar; +import org.springframework.integration.hazelcast.inbound.HazelcastClusterMonitorMessageProducer; +import org.springframework.integration.hazelcast.inbound.HazelcastContinuousQueryMessageProducer; +import org.springframework.integration.hazelcast.inbound.HazelcastDistributedSQLMessageSource; +import org.springframework.integration.hazelcast.inbound.HazelcastEventDrivenMessageProducer; +import org.springframework.messaging.PollableChannel; + +import com.hazelcast.collection.IList; +import com.hazelcast.collection.IQueue; +import com.hazelcast.collection.ISet; +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Configuration Class for Hazelcast Integration Inbound Test + * + * @author Eren Avsarogullari + * @author Artem Bilan + */ +@Configuration +@EnableIntegration +public class HazelcastIntegrationInboundTestConfiguration { + + @AfterClass + public void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Bean + public PollableChannel distributedMapChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel distributedMapChannel2() { + return new QueueChannel(); + } + + @Bean + public PollableChannel distributedListChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel distributedSetChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel distributedQueueChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel topicChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel multiMapChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel replicatedMapChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel cqDistributedMapChannel1() { + return new QueueChannel(); + } + + @Bean + public PollableChannel cqDistributedMapChannel2() { + return new QueueChannel(); + } + + @Bean + public PollableChannel cqDistributedMapChannel3() { + return new QueueChannel(); + } + + @Bean + public PollableChannel dsDistributedMapChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel dsDistributedMapChannel2() { + return new QueueChannel(); + } + + @Bean + public PollableChannel dsDistributedMapChannel3() { + return new QueueChannel(); + } + + @Bean + public PollableChannel dsDistributedMapChannel4() { + return new QueueChannel(); + } + + @Bean + public PollableChannel cmonChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel cmonChannel2() { + return new QueueChannel(); + } + + @Bean + public IMap testDistributedMap() { + return testHazelcastInstance().getMap("Test_Distributed_Map"); + } + + @Bean + public IMap testDistributedMap2() { + return testHazelcastInstance().getMap("Test_Distributed_Map2"); + } + + @Bean + public IList testDistributedList() { + return testHazelcastInstance().getList("Test_Distributed_List"); + } + + @Bean + public ISet testDistributedSet() { + return testHazelcastInstance().getSet("Test_Distributed_Set"); + } + + @Bean + public IQueue testDistributedQueue() { + return testHazelcastInstance().getQueue("Test_Distributed_Queue"); + } + + @Bean + public ITopic testTopic() { + return testHazelcastInstance().getTopic("Test_Topic"); + } + + @Bean + public MultiMap testMultiMap() { + return testHazelcastInstance().getMultiMap("Test_Multi_Map"); + } + + @Bean + public ReplicatedMap testReplicatedMap() { + return testHazelcastInstance().getReplicatedMap("Test_Replicated_Map"); + } + + @Bean + public IMap testCQDistributedMap1() { + return testHazelcastInstance().getMap("Test_CQ_Distributed_Map1"); + } + + @Bean + public IMap testCQDistributedMap2() { + return testHazelcastInstance().getMap("Test_CQ_Distributed_Map2"); + } + + @Bean + public IMap testCQDistributedMap3() { + return testHazelcastInstance().getMap("Test_CQ_Distributed_Map3"); + } + + @Bean + public IMap testDSDistributedMap() { + return testHazelcastInstance().getMap("Test_DS_Distributed_Map"); + } + + @Bean + public IMap testDSDistributedMap2() { + return testHazelcastInstance().getMap("Test_DS_Distributed_Map2"); + } + + @Bean + public IMap testDSDistributedMap3() { + return testHazelcastInstance().getMap("Test_DS_Distributed_Map3"); + } + + @Bean + public IMap testDSDistributedMap4() { + return testHazelcastInstance().getMap("Test_DS_Distributed_Map4"); + } + + @Bean + public Config hazelcastConfig() { + Config config = new Config(); + config.getCPSubsystemConfig().setCPMemberCount(0) + .setSessionHeartbeatIntervalSeconds(1); + return config; + } + + @Bean(destroyMethod = "") + public HazelcastInstance testHazelcastInstance() { + return Hazelcast.newHazelcastInstance(hazelcastConfig()); + } + + @Bean(HazelcastLocalInstanceRegistrar.BEAN_NAME) + public HazelcastLocalInstanceRegistrar hazelcastLocalInstanceRegistrar() { + return new HazelcastLocalInstanceRegistrar(testHazelcastInstance()); + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testDistributedMap()); + producer.setOutputChannel(distributedMapChannel()); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer2() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testDistributedMap2()); + producer.setOutputChannel(distributedMapChannel2()); + producer.setCacheEventTypes("ADDED,REMOVED,UPDATED,CLEAR_ALL"); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer3() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testDistributedList()); + producer.setOutputChannel(distributedListChannel()); + producer.setCacheEventTypes("ADDED,REMOVED"); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer4() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testDistributedSet()); + producer.setOutputChannel(distributedSetChannel()); + producer.setCacheEventTypes("ADDED,REMOVED"); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer5() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testDistributedQueue()); + producer.setOutputChannel(distributedQueueChannel()); + producer.setCacheEventTypes("ADDED,REMOVED"); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer6() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testTopic()); + producer.setOutputChannel(topicChannel()); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer7() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testReplicatedMap()); + producer.setOutputChannel(replicatedMapChannel()); + producer.setCacheEventTypes("ADDED,REMOVED,UPDATED"); + return producer; + } + + @Bean + public HazelcastEventDrivenMessageProducer hazelcastEventDrivenMessageProducer8() { + final HazelcastEventDrivenMessageProducer producer = + new HazelcastEventDrivenMessageProducer(testMultiMap()); + producer.setOutputChannel(multiMapChannel()); + producer.setCacheEventTypes("ADDED,REMOVED,CLEAR_ALL"); + return producer; + } + + @Bean + public HazelcastContinuousQueryMessageProducer hazelcastContinuousQueryMessageProducer() { + final HazelcastContinuousQueryMessageProducer producer = + new HazelcastContinuousQueryMessageProducer(testCQDistributedMap1(), + "name=TestName1"); + producer.setOutputChannel(cqDistributedMapChannel1()); + return producer; + } + + @Bean + public HazelcastContinuousQueryMessageProducer hazelcastContinuousQueryMessageProducer2() { + final HazelcastContinuousQueryMessageProducer producer = + new HazelcastContinuousQueryMessageProducer(testCQDistributedMap2(), + "name=TestName1 OR name=TestName2"); + producer.setOutputChannel(cqDistributedMapChannel2()); + producer.setCacheEventTypes("ADDED,REMOVED,UPDATED,CLEAR_ALL"); + return producer; + } + + @Bean + public HazelcastContinuousQueryMessageProducer hazelcastContinuousQueryMessageProducer3() { + final HazelcastContinuousQueryMessageProducer producer = + new HazelcastContinuousQueryMessageProducer(testCQDistributedMap3(), + "surname=TestSurname2"); + producer.setOutputChannel(cqDistributedMapChannel3()); + producer.setCacheEventTypes("UPDATED"); + producer.setIncludeValue(false); + return producer; + } + + @Bean + public HazelcastClusterMonitorMessageProducer hazelcastClusterMonitorMessageProducer() { + final HazelcastClusterMonitorMessageProducer producer = + new HazelcastClusterMonitorMessageProducer(testHazelcastInstance()); + producer.setOutputChannel(cmonChannel()); + return producer; + } + + @Bean + public HazelcastClusterMonitorMessageProducer hazelcastClusterMonitorMessageProducer2() { + final HazelcastClusterMonitorMessageProducer producer = + new HazelcastClusterMonitorMessageProducer(testHazelcastInstance()); + producer.setOutputChannel(cmonChannel2()); + producer.setMonitorEventTypes("DISTRIBUTED_OBJECT"); + return producer; + } + + @Bean + @InboundChannelAdapter(value = "dsDistributedMapChannel", + poller = @Poller(maxMessagesPerPoll = "1")) + public HazelcastDistributedSQLMessageSource hazelcastDistributedSQLMessageSource() { + final HazelcastDistributedSQLMessageSource messageSource = + new HazelcastDistributedSQLMessageSource(testDSDistributedMap(), + "name='TestName4' AND surname='TestSurname4'"); + messageSource.setIterationType(DistributedSQLIterationType.ENTRY); + return messageSource; + } + + @Bean + @InboundChannelAdapter(value = "dsDistributedMapChannel2", + poller = @Poller(maxMessagesPerPoll = "1")) + public HazelcastDistributedSQLMessageSource hazelcastDistributedSQLMessageSource2() { + final HazelcastDistributedSQLMessageSource messageSource = + new HazelcastDistributedSQLMessageSource(testDSDistributedMap2(), + "name='TestName1' AND surname='TestSurname1'"); + messageSource.setIterationType(DistributedSQLIterationType.KEY); + return messageSource; + } + + @Bean + @InboundChannelAdapter(value = "dsDistributedMapChannel3", + poller = @Poller(maxMessagesPerPoll = "1")) + public HazelcastDistributedSQLMessageSource hazelcastDistributedSQLMessageSource3() { + final HazelcastDistributedSQLMessageSource messageSource = + new HazelcastDistributedSQLMessageSource(testDSDistributedMap3(), + "age > 5"); + messageSource.setIterationType(DistributedSQLIterationType.KEY); + return messageSource; + } + + @Bean + @InboundChannelAdapter(value = "dsDistributedMapChannel4", + poller = @Poller(maxMessagesPerPoll = "1")) + public HazelcastDistributedSQLMessageSource hazelcastDistributedSQLMessageSource4() { + final HazelcastDistributedSQLMessageSource messageSource = + new HazelcastDistributedSQLMessageSource(testDSDistributedMap4(), + "name='TestName3' AND surname='TestSurname3'"); + messageSource.setIterationType(DistributedSQLIterationType.VALUE); + return messageSource; + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/util/HazelcastInboundChannelAdapterTestUtils.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/util/HazelcastInboundChannelAdapterTestUtils.java new file mode 100644 index 0000000000..40a9b196a7 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/inbound/util/HazelcastInboundChannelAdapterTestUtils.java @@ -0,0 +1,370 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.inbound.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.message.EntryEventMessagePayload; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; + +import com.hazelcast.cluster.MembershipEvent; +import com.hazelcast.collection.ICollection; +import com.hazelcast.core.DistributedObjectEvent; +import com.hazelcast.core.EntryEventType; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.spi.exception.DistributedObjectDestroyedException; +import com.hazelcast.topic.ITopic; + +/** + * Util Class for Hazelcast Inbound Channel Adapters Test Support. + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@SuppressWarnings("unchecked") +public final class HazelcastInboundChannelAdapterTestUtils { + + public static final int TIMEOUT = 30_000; + + public static void verifyEntryEvent(Message msg, String cacheName, + EntryEventType event) { + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + if (event == EntryEventType.CLEAR_ALL || event == EntryEventType.EVICT_ALL) { + assertThat(msg.getPayload() instanceof Integer).isTrue(); + } + else { + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + } + + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo(cacheName); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(event.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + } + + public static void verifyItemEvent(Message msg, EntryEventType event) { + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE).toString()).isEqualTo(event.toString()); + } + + public static void testEventDrivenForADDEDDistributedMapEntryEvent( + final IMap distributedMap, + final PollableChannel channel, final String cacheName) { + HazelcastIntegrationTestUser hazelcastIntegrationTestUser = + new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1"); + distributedMap.put(1, hazelcastIntegrationTestUser); + Message msg = channel.receive(TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.ADDED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo(cacheName); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(1)); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getId()).isEqualTo(1); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getName()).isEqualTo("TestName1"); + assertThat((((EntryEventMessagePayload) msg + .getPayload()).value).getSurname()).isEqualTo("TestSurname1"); + } + + public static void testEventDrivenForDistributedMapEntryEvents( + final IMap distributedMap, + final PollableChannel channel, final String cacheName) { + distributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + distributedMap.put(1, + new HazelcastIntegrationTestUser(1, "TestName1", "TestSurnameUpdated")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.UPDATED); + + distributedMap.remove(1); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.REMOVED); + + distributedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + distributedMap.clear(); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.CLEAR_ALL); + } + + public static void testEventDrivenForDistributedCollectionItemEvents( + final ICollection distributedObject, + final PollableChannel channel) { + HazelcastIntegrationTestUser user = + new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1"); + distributedObject.add(user); + Message msg = channel.receive(TIMEOUT); + verifyItemEvent(msg, EntryEventType.ADDED); + + distributedObject.remove(user); + msg = channel.receive(TIMEOUT); + verifyItemEvent(msg, EntryEventType.REMOVED); + + user = new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2"); + distributedObject.add(user); + msg = channel.receive(TIMEOUT); + verifyItemEvent(msg, EntryEventType.ADDED); + } + + public static void testEventDrivenForReplicatedMapEntryEvents( + final ReplicatedMap replicatedMap, + final PollableChannel channel, final String cacheName) { + replicatedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + replicatedMap.put(1, + new HazelcastIntegrationTestUser(1, "TestName1", "TestSurnameUpdated")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.UPDATED); + + replicatedMap.remove(1); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.REMOVED); + + replicatedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + } + + public static void testEventDrivenForTopicMessageEvent( + final ITopic topic, final PollableChannel channel) { + topic.publish(new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = channel.receive(TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.PUBLISHING_TIME)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo(topic.getName()); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getId()).isEqualTo(1); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getName()).isEqualTo("TestName1"); + assertThat(((HazelcastIntegrationTestUser) msg.getPayload()).getSurname()).isEqualTo("TestSurname1"); + } + + public static void testEventDrivenForMultiMapEntryEvents( + final MultiMap multiMap, + final PollableChannel channel, final String cacheName) { + multiMap.put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + Message msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + multiMap.put(1, + new HazelcastIntegrationTestUser(1, "TestName1", "TestSurnameUpdated")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + multiMap.remove(1); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.REMOVED); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.REMOVED); + + multiMap.put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.ADDED); + + multiMap.clear(); + msg = channel.receive(TIMEOUT); + verifyEntryEvent(msg, cacheName, EntryEventType.CLEAR_ALL); + } + + public static void testContinuousQueryForUPDATEDEntryEventWhenIncludeValueIsFalse( + final IMap cqDistributedMap, + final PollableChannel channel, final String cacheName) { + cqDistributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1")); + cqDistributedMap + .put(1, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2")); + Message msg = channel.receive(TIMEOUT); + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof EntryEventMessagePayload).isTrue(); + assertThat(msg.getHeaders().get(HazelcastHeaders.MEMBER)).isNotNull(); + assertThat(msg.getHeaders().get(HazelcastHeaders.EVENT_TYPE)).isEqualTo(EntryEventType.UPDATED.name()); + assertThat(msg.getHeaders().get(HazelcastHeaders.CACHE_NAME)).isEqualTo(cacheName); + + assertThat(((EntryEventMessagePayload) msg + .getPayload()).key).isEqualTo(Integer.valueOf(1)); + assertThat(((EntryEventMessagePayload) msg + .getPayload()).oldValue).isNull(); + assertThat(((EntryEventMessagePayload) msg + .getPayload()).value).isNull(); + } + + public static void testDistributedSQLForENTRYIterationType( + final IMap dsDistributedMap, + final PollableChannel channel) { + dsDistributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1", 10)); + dsDistributedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2", 20)); + dsDistributedMap + .put(3, new HazelcastIntegrationTestUser(3, "TestName3", "TestSurname3", 30)); + dsDistributedMap + .put(4, new HazelcastIntegrationTestUser(4, "TestName4", "TestSurname4", 40)); + dsDistributedMap + .put(5, new HazelcastIntegrationTestUser(5, "TestName5", "TestSurname5", 50)); + + Message msg = channel.receive(TIMEOUT); + + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof Collection).isTrue(); + assertThat((((Map.Entry) ((Collection) msg.getPayload()).iterator().next()) + .getKey())).isEqualTo(4); + assertThat(((HazelcastIntegrationTestUser) ((Map.Entry) ((Collection) msg + .getPayload()).iterator().next()).getValue()).getId()).isEqualTo(4); + assertThat(((HazelcastIntegrationTestUser) ((Map.Entry) ((Collection) msg + .getPayload()).iterator().next()).getValue()).getName()).isEqualTo("TestName4"); + assertThat(((HazelcastIntegrationTestUser) ((Map.Entry) ((Collection) msg + .getPayload()).iterator().next()).getValue()).getSurname()).isEqualTo("TestSurname4"); + } + + public static void testDistributedSQLForKEYIterationType( + final IMap dsDistributedMap, + final PollableChannel channel) { + dsDistributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1", 10)); + dsDistributedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2", 20)); + dsDistributedMap + .put(3, new HazelcastIntegrationTestUser(3, "TestName3", "TestSurname3", 30)); + + Message msg = channel.receive(TIMEOUT); + + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof Collection).isTrue(); + assertThat(((Collection) msg.getPayload()).iterator().next()).isEqualTo(1); + } + + public static void testDistributedSQLForLOCAL_KEYIterationType( + final IMap dsDistributedMap, + final PollableChannel channel) { + dsDistributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1", 10)); + dsDistributedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2", 20)); + dsDistributedMap + .put(3, new HazelcastIntegrationTestUser(3, "TestName3", "TestSurname3", 30)); + + Message msg = channel.receive(TIMEOUT); + + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof Collection).isTrue(); + } + + public static void testDistributedSQLForVALUEIterationType( + final IMap dsDistributedMap, + final PollableChannel channel) { + dsDistributedMap + .put(1, new HazelcastIntegrationTestUser(1, "TestName1", "TestSurname1", 10)); + dsDistributedMap + .put(2, new HazelcastIntegrationTestUser(2, "TestName2", "TestSurname2", 20)); + dsDistributedMap + .put(3, new HazelcastIntegrationTestUser(3, "TestName3", "TestSurname3", 30)); + dsDistributedMap + .put(4, new HazelcastIntegrationTestUser(4, "TestName4", "TestSurname4", 40)); + dsDistributedMap + .put(5, new HazelcastIntegrationTestUser(5, "TestName5", "TestSurname5", 50)); + + Message msg = channel.receive(TIMEOUT); + + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof Collection).isTrue(); + assertThat(((HazelcastIntegrationTestUser) (((Collection) msg.getPayload()).iterator() + .next())).getId()).isEqualTo(3); + assertThat(((HazelcastIntegrationTestUser) (((Collection) msg.getPayload()).iterator() + .next())).getName()).isEqualTo("TestName3"); + assertThat(((HazelcastIntegrationTestUser) (((Collection) msg.getPayload()).iterator() + .next())).getSurname()).isEqualTo("TestSurname3"); + } + + public static void testDistributedObjectEventByChannelAndHazelcastInstance( + final PollableChannel channel, final HazelcastInstance hazelcastInstance, + final String distributedObjectName) { + final IMap distributedMap = + hazelcastInstance.getMap(distributedObjectName); + + Message msg = channel.receive(TIMEOUT); + verifyDistributedObjectEvent(msg, DistributedObjectEvent.EventType.CREATED, + distributedObjectName); + + distributedMap.destroy(); + + msg = channel.receive(TIMEOUT); + try { + // Since Hazelcast 3.6 we can use DistributedObjectEvent.getDistributedObject() for DESTROYED objects. + verifyDistributedObjectEvent(msg, DistributedObjectEvent.EventType.DESTROYED, distributedObjectName); + fail("DistributedObjectDestroyedException expected"); + } + catch (Exception e) { + assertThat(e).isInstanceOf(DistributedObjectDestroyedException.class); + } + } + + private static void verifyMembershipEvent(final Message msg, + final int membershipEvent) { + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof MembershipEvent).isTrue(); + assertThat(((MembershipEvent) msg.getPayload()).getEventType()).isEqualTo(membershipEvent); + assertThat(((MembershipEvent) msg.getPayload()).getMember()).isNotNull(); + } + + private static void verifyDistributedObjectEvent(final Message msg, + final DistributedObjectEvent.EventType eventType, + final String distributedObjectName) { + assertThat(msg).isNotNull(); + assertThat(msg.getPayload()).isNotNull(); + assertThat(msg.getPayload() instanceof DistributedObjectEvent).isTrue(); + assertThat(((DistributedObjectEvent) msg.getPayload()).getEventType()).isEqualTo(eventType); + assertThat(distributedObjectName).as((((DistributedObjectEvent) msg.getPayload()).getDistributedObject()) + .getName()).isNotNull(); + } + + private HazelcastInboundChannelAdapterTestUtils() { + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java new file mode 100644 index 0000000000..455e27501c --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java @@ -0,0 +1,311 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.leader; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.spy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.leader.Context; +import org.springframework.integration.leader.DefaultCandidate; +import org.springframework.integration.leader.event.AbstractLeaderEvent; +import org.springframework.integration.leader.event.DefaultLeaderEventPublisher; +import org.springframework.integration.leader.event.LeaderEventPublisher; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * Tests for hazelcast leader election. + * + * @author Janne Valkealahti + * @author Patrick Peralta + * @author Dave Syer + * @author Artem Bilan + * @author Mael Le Guével + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +public class LeaderInitiatorTests { + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Autowired + private TestCandidate candidate; + + @Autowired + private TestEventListener listener; + + @Autowired + private LeaderInitiator initiator; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testLeaderElections() throws Exception { + assertThat(this.candidate.onGrantedLatch.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(this.listener.onEventLatch.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(this.listener.events.size()).isEqualTo(1); + + this.initiator.destroy(); + + CountDownLatch granted = new CountDownLatch(1); + CountingPublisher countingPublisher = new CountingPublisher(granted); + List initiators = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + LeaderInitiator initiator = new LeaderInitiator(this.hazelcastInstance, new DefaultCandidate()); + initiator.setLeaderEventPublisher(countingPublisher); + initiators.add(initiator); + } + + for (LeaderInitiator initiator : initiators) { + initiator.start(); + } + + assertThat(granted.await(10, TimeUnit.SECONDS)).isTrue(); + + LeaderInitiator initiator1 = countingPublisher.initiator; + + LeaderInitiator initiator2 = null; + + for (LeaderInitiator initiator : initiators) { + if (initiator != initiator1) { + initiator2 = initiator; + break; + } + } + + assertThat(initiator2).isNotNull(); + + assertThat(initiator1.getContext().isLeader()).isTrue(); + assertThat(initiator2.getContext().isLeader()).isFalse(); + + final CountDownLatch granted1 = new CountDownLatch(1); + final CountDownLatch granted2 = new CountDownLatch(1); + CountDownLatch revoked1 = new CountDownLatch(1); + CountDownLatch revoked2 = new CountDownLatch(1); + initiator1.setLeaderEventPublisher(new CountingPublisher(granted1, revoked1) { + + @Override + public void publishOnRevoked(Object source, Context context, String role) { + try { + // It's difficult to see round-robin election, so block one initiator until the second is elected. + assertThat(granted2.await(10, TimeUnit.SECONDS)).isTrue(); + } + catch (InterruptedException e) { + // No op + } + super.publishOnRevoked(source, context, role); + } + + }); + + initiator2.setLeaderEventPublisher(new CountingPublisher(granted2, revoked2) { + + @Override + public void publishOnRevoked(Object source, Context context, String role) { + try { + // It's difficult to see round-robin election, so block one initiator until the second is elected. + assertThat(granted1.await(10, TimeUnit.SECONDS)).isTrue(); + } + catch (InterruptedException e) { + // No op + } + super.publishOnRevoked(source, context, role); + } + + }); + + initiator1.getContext().yield(); + + assertThat(revoked1.await(10, TimeUnit.SECONDS)).isTrue(); + + assertThat(initiator2.getContext().isLeader()).isTrue(); + assertThat(initiator1.getContext().isLeader()).isFalse(); + + initiator2.getContext().yield(); + + assertThat(revoked2.await(10, TimeUnit.SECONDS)).isTrue(); + + assertThat(initiator1.getContext().isLeader()).isTrue(); + assertThat(initiator2.getContext().isLeader()).isFalse(); + + initiator2.destroy(); + + CountDownLatch revoked11 = new CountDownLatch(1); + initiator1.setLeaderEventPublisher(new CountingPublisher(new CountDownLatch(1), revoked11)); + + initiator1.getContext().yield(); + + assertThat(revoked11.await(10, TimeUnit.SECONDS)).isTrue(); + + initiator1.destroy(); + + + CountDownLatch onGranted = new CountDownLatch(1); + + DefaultCandidate candidate = spy(new DefaultCandidate()); + willAnswer(invocation -> { + try { + return invocation.callRealMethod(); + } + finally { + onGranted.countDown(); + } + }) + .given(candidate).onGranted(any(Context.class)); + + LeaderInitiator initiator = new LeaderInitiator(this.hazelcastInstance, candidate); + + initiator.setLeaderEventPublisher(new DefaultLeaderEventPublisher() { + + @Override + public void publishOnGranted(Object source, Context context, String role) { + throw new RuntimeException("intentional"); + } + + }); + + initiator.start(); + + assertThat(onGranted.await(5, TimeUnit.SECONDS)).isTrue(); + + assertThat(initiator.getContext().isLeader()).isTrue(); + + initiator.destroy(); + } + + + @Configuration + public static class TestConfig { + + @Bean + public TestCandidate candidate() { + return new TestCandidate(); + } + + @Bean + public Config hazelcastConfig() { + Config config = new Config(); + config.getCPSubsystemConfig() + .setSessionHeartbeatIntervalSeconds(1); + return config; + } + + @Bean(destroyMethod = "") + public HazelcastInstance hazelcastInstance() { + return Hazelcast.newHazelcastInstance(hazelcastConfig()); + } + + @Bean + public LeaderInitiator initiator() { + return new LeaderInitiator(hazelcastInstance(), candidate()); + } + + @Bean + public TestEventListener testEventListener() { + return new TestEventListener(); + } + + } + + static class TestCandidate extends DefaultCandidate { + + CountDownLatch onGrantedLatch = new CountDownLatch(1); + + @Override + public void onGranted(Context ctx) { + this.onGrantedLatch.countDown(); + super.onGranted(ctx); + } + + } + + static class TestEventListener implements ApplicationListener { + + CountDownLatch onEventLatch = new CountDownLatch(1); + + ArrayList events = new ArrayList<>(); + + @Override + public void onApplicationEvent(AbstractLeaderEvent event) { + this.events.add(event); + this.onEventLatch.countDown(); + } + + } + + private static class CountingPublisher implements LeaderEventPublisher { + + private CountDownLatch granted; + + private CountDownLatch revoked; + + private volatile LeaderInitiator initiator; + + CountingPublisher(CountDownLatch granted, CountDownLatch revoked) { + this.granted = granted; + this.revoked = revoked; + } + + CountingPublisher(CountDownLatch granted) { + this(granted, new CountDownLatch(1)); + } + + @Override + public void publishOnRevoked(Object source, Context context, String role) { + this.revoked.countDown(); + } + + @Override + public void publishOnFailedToAcquire(Object source, Context context, String role) { + + } + + @Override + public void publishOnGranted(Object source, Context context, String role) { + this.initiator = (LeaderInitiator) source; + this.granted.countDown(); + } + + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistryTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistryTests.java new file mode 100644 index 0000000000..4cc8355b62 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/lock/HazelcastLockRegistryTests.java @@ -0,0 +1,276 @@ +/* + * Copyright 2017-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.lock; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; + +import org.junit.AfterClass; +import org.junit.Test; + +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.cp.lock.FencedLock; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; + +/** + * @author Artem Bilan + */ +public class HazelcastLockRegistryTests { + + private static final Config CONFIG = new Config(); + + static { + CONFIG.getCPSubsystemConfig().setCPMemberCount(0); + } + + private static final HazelcastInstance instance = Hazelcast.newHazelcastInstance(CONFIG); + + @AfterClass + public static void destroy() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testLock() { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock = registry.obtain("foo"); + lock.lock(); + try { + assertThat(((FencedLock) lock).isLocked()).isTrue(); + assertThat(((FencedLock) lock).isLockedByCurrentThread()).isTrue(); + } + finally { + lock.unlock(); + } + } + } + + @Test + public void testLockInterruptibly() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock = registry.obtain("foo"); + lock.lockInterruptibly(); + try { + assertThat(((FencedLock) lock).isLocked()).isTrue(); + assertThat(((FencedLock) lock).isLockedByCurrentThread()).isTrue(); + } + finally { + lock.unlock(); + } + } + } + + @Test + public void testReentrantLock() { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock1 = registry.obtain("foo"); + lock1.lock(); + try { + Lock lock2 = registry.obtain("foo"); + assertThat(lock2).isSameAs(lock1); + lock2.lock(); + lock2.unlock(); + } + finally { + lock1.unlock(); + } + } + } + + @Test + public void testReentrantLockInterruptibly() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock1 = registry.obtain("foo"); + lock1.lockInterruptibly(); + try { + Lock lock2 = registry.obtain("foo"); + assertThat(lock2).isSameAs(lock1); + lock2.lockInterruptibly(); + lock2.unlock(); + } + finally { + lock1.unlock(); + } + } + } + + @Test + public void testTwoLocks() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock1 = registry.obtain("foo"); + lock1.lockInterruptibly(); + try { + Lock lock2 = registry.obtain("bar"); + assertThat(lock2).isNotSameAs(lock1); + lock2.lockInterruptibly(); + lock2.unlock(); + } + finally { + lock1.unlock(); + } + } + } + + @Test + public void testTwoThreadsSecondFailsToGetLock() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + Lock lock1 = registry.obtain("foo"); + lock1.lockInterruptibly(); + AtomicBoolean locked = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + Future result = Executors.newSingleThreadExecutor().submit(() -> { + Lock lock2 = registry.obtain("foo"); + locked.set(lock2.tryLock(200, TimeUnit.MILLISECONDS)); + latch.countDown(); + try { + lock2.unlock(); + } + catch (Exception e) { + return e; + } + return null; + }); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isFalse(); + lock1.unlock(); + Object ise = result.get(10, TimeUnit.SECONDS); + assertThat(ise).isInstanceOf(IllegalMonitorStateException.class); + assertThat(((Exception) ise).getMessage()).contains("Current thread is not owner of the lock!"); + } + + @Test + public void testTwoThreads() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + Lock lock1 = registry.obtain("foo"); + AtomicBoolean locked = new AtomicBoolean(); + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + CountDownLatch latch3 = new CountDownLatch(1); + lock1.lockInterruptibly(); + Executors.newSingleThreadExecutor().execute(() -> { + Lock lock2 = registry.obtain("foo"); + try { + latch1.countDown(); + lock2.lockInterruptibly(); + latch2.await(10, TimeUnit.SECONDS); + locked.set(true); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + finally { + lock2.unlock(); + latch3.countDown(); + } + }); + assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isFalse(); + lock1.unlock(); + latch2.countDown(); + assertThat(latch3.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isTrue(); + } + + @Test + public void testTwoThreadsDifferentRegistries() throws Exception { + HazelcastLockRegistry registry1 = new HazelcastLockRegistry(instance); + HazelcastLockRegistry registry2 = new HazelcastLockRegistry(instance); + Lock lock1 = registry1.obtain("foo"); + AtomicBoolean locked = new AtomicBoolean(); + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + CountDownLatch latch3 = new CountDownLatch(1); + lock1.lockInterruptibly(); + Executors.newSingleThreadExecutor().execute(() -> { + Lock lock2 = registry2.obtain("foo"); + try { + latch1.countDown(); + lock2.lockInterruptibly(); + latch2.await(10, TimeUnit.SECONDS); + locked.set(true); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + finally { + lock2.unlock(); + latch3.countDown(); + } + }); + assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isFalse(); + lock1.unlock(); + latch2.countDown(); + assertThat(latch3.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isTrue(); + } + + @Test + public void testTwoThreadsWrongOneUnlocks() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + final Lock lock = registry.obtain("foo"); + lock.lockInterruptibly(); + final AtomicBoolean locked = new AtomicBoolean(); + final CountDownLatch latch = new CountDownLatch(1); + Future result = Executors.newSingleThreadExecutor().submit(() -> { + try { + lock.unlock(); + } + catch (Exception e) { + latch.countDown(); + return e; + } + return null; + }); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(locked.get()).isFalse(); + lock.unlock(); + Object imse = result.get(10, TimeUnit.SECONDS); + assertThat(imse).isInstanceOf(IllegalMonitorStateException.class); + assertThat(((Exception) imse).getMessage()).contains("Current thread is not owner of the lock!"); + } + + @Test + public void testTryLock() throws Exception { + HazelcastLockRegistry registry = new HazelcastLockRegistry(instance); + for (int i = 0; i < 10; i++) { + Lock lock = registry.obtain("foo"); + + int n = 0; + while (!lock.tryLock() && n++ < 100) { + Thread.sleep(100); + } + assertThat(n).isLessThan(100); + + lock.unlock(); + } + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStoreTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStoreTests.java new file mode 100644 index 0000000000..58046227c2 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/metadata/HazelcastMetadataStoreTests.java @@ -0,0 +1,181 @@ +/* + * Copyright 2017-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.metadata; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.springframework.integration.metadata.MetadataStoreListener; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; + +/** + * @author Vinicius Carvalho + */ +public class HazelcastMetadataStoreTests { + + private static HazelcastInstance instance; + + private static IMap map; + + HazelcastMetadataStore metadataStore; + + @BeforeClass + public static void init() { + instance = Hazelcast.newHazelcastInstance(); + map = instance.getMap("customTestsMetadataStore"); + } + + @AfterClass + public static void destroy() { + instance.getLifecycleService().terminate(); + } + + @Before + public void setup() throws Exception { + this.metadataStore = new HazelcastMetadataStore(map); + this.metadataStore.afterPropertiesSet(); + } + + @After + public void clean() { + map.clear(); + } + + @Test + public void testGetNonExistingKeyValue() { + String retrievedValue = this.metadataStore.get("does-not-exist"); + assertThat(retrievedValue).isNull(); + } + + @Test + public void testPersistKeyValue() { + this.metadataStore.put("HazelcastMetadataStoreTests-Spring", "Integration"); + assertThat(map.get("HazelcastMetadataStoreTests-Spring")).isEqualTo("Integration"); + } + + @Test + public void testGetValueFromMetadataStore() { + this.metadataStore.put("HazelcastMetadataStoreTests-GetValue", "Hello Hazelcast"); + String retrievedValue = this.metadataStore + .get("HazelcastMetadataStoreTests-GetValue"); + assertThat(retrievedValue).isEqualTo("Hello Hazelcast"); + } + + @Test + public void testPersistEmptyStringToMetadataStore() { + this.metadataStore.put("HazelcastMetadataStoreTests-PersistEmpty", ""); + + String retrievedValue = this.metadataStore + .get("HazelcastMetadataStoreTests-PersistEmpty"); + assertThat(retrievedValue).isEqualTo(""); + } + + @Test + public void testPersistNullStringToMetadataStore() { + try { + this.metadataStore.put("HazelcastMetadataStoreTests-PersistEmpty", null); + fail("Expected an IllegalArgumentException to be thrown."); + } + catch (IllegalArgumentException e) { + assertThat(e.getMessage()).isEqualTo("'value' must not be null."); + } + } + + @Test + public void testPersistWithEmptyKeyToMetadataStore() { + this.metadataStore.put("", "PersistWithEmptyKey"); + + String retrievedValue = this.metadataStore.get(""); + assertThat(retrievedValue).isEqualTo("PersistWithEmptyKey"); + } + + @Test + public void testPersistWithNullKeyToMetadataStore() { + try { + this.metadataStore.put(null, "something"); + fail("Expected an IllegalArgumentException to be thrown."); + + } + catch (IllegalArgumentException e) { + assertThat(e.getMessage()).isEqualTo("'key' must not be null."); + } + } + + @Test + public void testGetValueWithNullKeyFromMetadataStore() { + try { + this.metadataStore.get(null); + } + catch (IllegalArgumentException e) { + assertThat(e.getMessage()).isEqualTo("'key' must not be null."); + return; + } + + fail("Expected an IllegalArgumentException to be thrown."); + } + + @Test + public void testRemoveFromMetadataStore() { + String testKey = "HazelcastMetadataStoreTests-Remove"; + String testValue = "Integration"; + + this.metadataStore.put(testKey, testValue); + + assertThat(this.metadataStore.remove(testKey)).isEqualTo(testValue); + assertThat(this.metadataStore.remove(testKey)).isNull(); + } + + @Test + public void testPersistKeyValueIfAbsent() { + this.metadataStore.putIfAbsent("HazelcastMetadataStoreTests-Spring", + "Integration"); + assertThat(map.get("HazelcastMetadataStoreTests-Spring")).isEqualTo("Integration"); + } + + @Test + public void testReplaceValue() { + this.metadataStore.put("key", "old"); + assertThat(map.get("key")).isEqualTo("old"); + this.metadataStore.replace("key", "old", "new"); + assertThat(map.get("key")).isEqualTo("new"); + } + + @Test + public void testListener() { + MetadataStoreListener listener = mock(MetadataStoreListener.class); + this.metadataStore.addListener(listener); + + this.metadataStore.put("foo", "bar"); + this.metadataStore.replace("foo", "bar", "baz"); + this.metadataStore.remove("foo"); + verify(listener).onAdd("foo", "bar"); + verify(listener).onUpdate("foo", "baz"); + verify(listener).onRemove("foo", "baz"); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests-context.xml b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..c1638f0667 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests-context.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests.java new file mode 100644 index 0000000000..fb7480206a --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/HazelcastOutboundChannelAdapterTests.java @@ -0,0 +1,477 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.outbound; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.hazelcast.HazelcastHeaders; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvice; +import org.springframework.integration.hazelcast.outbound.util.HazelcastOutboundChannelAdapterTestUtils; +import org.springframework.integration.support.DefaultMessageBuilderFactory; +import org.springframework.integration.support.MessageBuilderFactory; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandlingException; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Hazelcast Outbound Channel Adapter Test Class. + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@DirtiesContext +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class HazelcastOutboundChannelAdapterTests { + + private static final String DISTRIBUTED_MAP = "distributedMap"; + + private static final String CACHE_HEADER = "CACHE_HEADER"; + + private final MessageBuilderFactory messageBuilderFactory = + new DefaultMessageBuilderFactory(); + + @Autowired + @Qualifier("firstMapChannel") + private MessageChannel firstMapChannel; + + @Autowired + @Qualifier("secondMapChannel") + private MessageChannel secondMapChannel; + + @Autowired + @Qualifier("thirdMapChannel") + private MessageChannel thirdMapChannel; + + @Autowired + @Qualifier("fourthMapChannel") + private MessageChannel fourthMapChannel; + + @Autowired + @Qualifier("fifthMapChannel") + private MessageChannel fifthMapChannel; + + @Autowired + @Qualifier("sixthMapChannel") + private MessageChannel sixthMapChannel; + + @Autowired + @Qualifier("bulkMapChannel") + private MessageChannel bulkMapChannel; + + @Autowired + @Qualifier("multiMapChannel") + private MessageChannel multiMapChannel; + + @Autowired + @Qualifier("replicatedMapChannel") + private MessageChannel replicatedMapChannel; + + @Autowired + @Qualifier("bulkReplicatedMapChannel") + private MessageChannel bulkReplicatedMapChannel; + + @Autowired + @Qualifier("listChannel") + private MessageChannel listChannel; + + @Autowired + @Qualifier("bulkListChannel") + private MessageChannel bulkListChannel; + + @Autowired + @Qualifier("setChannel") + private MessageChannel setChannel; + + @Autowired + @Qualifier("bulkSetChannel") + private MessageChannel bulkSetChannel; + + @Autowired + @Qualifier("queueChannel") + private MessageChannel queueChannel; + + @Autowired + @Qualifier("bulkQueueChannel") + private MessageChannel bulkQueueChannel; + + @Autowired + @Qualifier("topicChannel") + private MessageChannel topicChannel; + + @Autowired + @Qualifier("lockChannel") + private MessageChannel lockChannel; + + @Autowired + private Map distributedMap; + + @Autowired + private Map distributedBulkMap; + + @Autowired + private MultiMap multiMap; + + @Autowired + private ReplicatedMap replicatedMap; + + @Autowired + private ReplicatedMap bulkReplicatedMap; + + @Autowired + private List distributedList; + + @Autowired + private List distributedBulkList; + + @Autowired + private Set distributedSet; + + @Autowired + private Set distributedBulkSet; + + @Autowired + private Queue distributedQueue; + + @Autowired + private Queue distributedBulkQueue; + + @Autowired + private ITopic topic; + + @Autowired + @Qualifier("testFirstMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testFirstMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testSecondMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testSecondMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testThirdMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testThirdMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testFourthMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testFourthMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testBulkMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testBulkMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testMultiMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testMultiMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testReplicatedMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testReplicatedMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testBulkReplicatedMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testBulkReplicatedMapRequestHandlerAdvice; + + @Autowired + @Qualifier("testListRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testListRequestHandlerAdvice; + + @Autowired + @Qualifier("testBulkListRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testBulkListRequestHandlerAdvice; + + @Autowired + @Qualifier("testSetRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testSetRequestHandlerAdvice; + + @Autowired + @Qualifier("testBulkSetRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testBulkSetRequestHandlerAdvice; + + @Autowired + @Qualifier("testQueueRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testQueueRequestHandlerAdvice; + + @Autowired + @Qualifier("testBulkQueueRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testBulkQueueRequestHandlerAdvice; + + @Autowired + @Qualifier("testTopicRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice testTopicRequestHandlerAdvice; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Before + public void setUp() { + this.distributedMap.clear(); + this.distributedBulkMap.clear(); + this.distributedList.clear(); + this.distributedBulkList.clear(); + this.distributedSet.clear(); + this.distributedBulkSet.clear(); + this.distributedQueue.clear(); + this.distributedBulkQueue.clear(); + this.multiMap.clear(); + this.replicatedMap.clear(); + this.bulkReplicatedMap.clear(); + } + + @Test + public void testWriteToDistributedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedMap(this.firstMapChannel, this.distributedMap, + this.testFirstMapRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToDistributedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testBulkWriteToDistributedMap(this.bulkMapChannel, this.distributedBulkMap, + this.testBulkMapRequestHandlerAdvice); + } + + @Test + public void testWriteToDistributedMapWhenCacheExpressionIsSet() + throws InterruptedException { + sendMessageWithCacheHeaderToChannel(this.secondMapChannel, CACHE_HEADER, + DISTRIBUTED_MAP); + assertThat(this.testSecondMapRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyMapForPayload(new TreeMap(this.distributedMap)); + } + + @Test + public void testWriteToDistributedMapWhenHazelcastHeaderIsSet() + throws InterruptedException { + sendMessageWithCacheHeaderToChannel(this.thirdMapChannel, + HazelcastHeaders.CACHE_NAME, DISTRIBUTED_MAP); + assertThat(this.testThirdMapRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyMapForPayload(new TreeMap(this.distributedMap)); + } + + @Test + public void testWriteToDistributedMapWhenExtractPayloadIsFalse() + throws InterruptedException { + sendMessageWithCacheHeaderToChannel(this.fourthMapChannel, + HazelcastHeaders.CACHE_NAME, DISTRIBUTED_MAP); + assertThat(this.testFourthMapRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + verifyMapForMessage(new TreeMap(this.distributedMap)); + } + + @Test + public void testWriteToMultiMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToMultiMap(this.multiMapChannel, this.multiMap, + this.testMultiMapRequestHandlerAdvice); + } + + @Test + public void testWriteToReplicatedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToReplicatedMap(this.replicatedMapChannel, this.replicatedMap, + this.testReplicatedMapRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToReplicatedMap() throws InterruptedException { + Map userMap = + new HashMap<>(HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + for (int index = 1; + index <= HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT; index++) { + userMap + .put(index, HazelcastOutboundChannelAdapterTestUtils.getTestUser(index)); + } + + this.bulkReplicatedMapChannel.send(new GenericMessage<>(userMap)); + + assertThat(this.testBulkReplicatedMapRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyMapForPayload(new TreeMap(this.bulkReplicatedMap)); + } + + + @Test + public void testWriteToDistributedList() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedList(this.listChannel, this.distributedList, + this.testListRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToDistributedList() throws InterruptedException { + List userList = + new ArrayList<>(HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + for (int index = 1; + index <= HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT; index++) { + userList.add(HazelcastOutboundChannelAdapterTestUtils.getTestUser(index)); + } + + this.bulkListChannel.send(new GenericMessage<>(userList)); + + assertThat(this.testBulkListRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyCollection(this.distributedBulkList, + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Test + public void testWriteToDistributedSet() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedSet(this.setChannel, this.distributedSet, + this.testSetRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToDistributedSet() throws InterruptedException { + Set userSet = + new HashSet<>(HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + for (int index = 1; + index <= HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT; index++) { + userSet.add(HazelcastOutboundChannelAdapterTestUtils.getTestUser(index)); + } + + this.bulkSetChannel.send(new GenericMessage<>(userSet)); + + assertThat(this.testBulkSetRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + final List list = + new ArrayList(this.distributedBulkSet); + Collections.sort(list); + HazelcastOutboundChannelAdapterTestUtils + .verifyCollection(list, HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Test + public void testWriteToDistributedQueue() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedQueue(this.queueChannel, this.distributedQueue, + this.testQueueRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToDistributedQueue() throws InterruptedException { + Queue userQueue = + new ArrayBlockingQueue(HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + for (int index = 1; + index <= HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT; index++) { + userQueue.add(HazelcastOutboundChannelAdapterTestUtils.getTestUser(index)); + } + + this.bulkQueueChannel.send(new GenericMessage<>(userQueue)); + + assertThat(this.testBulkQueueRequestHandlerAdvice.executeLatch + .await(10, TimeUnit.SECONDS)).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyCollection(this.distributedBulkQueue, + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Test + public void testWriteToTopic() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToTopic(this.topicChannel, this.topic, + this.testTopicRequestHandlerAdvice); + } + + @Test(expected = MessageHandlingException.class) + public void testWriteToDistributedMapWhenCacheIsNotSet() { + this.fifthMapChannel.send(new GenericMessage<>( + HazelcastOutboundChannelAdapterTestUtils.getTestUser(1))); + } + + @Test(expected = MessageHandlingException.class) + public void testWriteToDistributedMapWhenKeyExpressionIsNotSet() { + Message message = this.messageBuilderFactory + .withPayload(HazelcastOutboundChannelAdapterTestUtils.getTestUser(1)) + .setHeader(HazelcastHeaders.CACHE_NAME, DISTRIBUTED_MAP).build(); + this.sixthMapChannel.send(message); + } + + @Test(expected = MessageHandlingException.class) + public void testWriteToLock() { + this.lockChannel.send(new GenericMessage<>("foo")); + } + + private void sendMessageWithCacheHeaderToChannel(final MessageChannel channel, + final String headerName, final String distributedObjectName) { + for (int index = 1; + index <= HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT; index++) { + Message message = this.messageBuilderFactory + .withPayload(HazelcastOutboundChannelAdapterTestUtils.getTestUser(index)) + .setHeader(headerName, distributedObjectName).build(); + channel.send(message); + } + } + + private void verifyMapForMessage( + final Map> map) { + int index = 1; + assertThat(map).isNotNull(); + assertThat(map.size()).isEqualTo(HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + for (Entry> entry : map.entrySet()) { + assertThat(entry).isNotNull(); + assertThat(entry.getKey().intValue()).isEqualTo(index); + assertThat(entry.getValue().getHeaders().size() > 0).isTrue(); + HazelcastOutboundChannelAdapterTestUtils + .verifyHazelcastIntegrationTestUser(entry.getValue().getPayload(), index); + index++; + } + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastIntegrationOutboundTestConfiguration.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastIntegrationOutboundTestConfiguration.java new file mode 100644 index 0000000000..5e979e388d --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastIntegrationOutboundTestConfiguration.java @@ -0,0 +1,310 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.outbound.config; + +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import org.junit.AfterClass; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvice; +import org.springframework.integration.hazelcast.outbound.HazelcastCacheWritingMessageHandler; +import org.springframework.integration.hazelcast.outbound.util.HazelcastOutboundChannelAdapterTestUtils; +import org.springframework.messaging.MessageChannel; + +import com.hazelcast.core.DistributedObject; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Configuration Class for Hazelcast Integration Outbound Test + * + * @author Eren Avsarogullari + * @author Artem Bilan + * + * @since 6.0 + */ +@Configuration +@ComponentScan(basePackages = { "org.springframework.integration.hazelcast.*" }) +@EnableIntegration +@IntegrationComponentScan("org.springframework.integration.hazelcast.outbound") +public class HazelcastIntegrationOutboundTestConfiguration { + + @AfterClass + public void terminate() { + HazelcastInstanceFactory.terminateAll(); + } + + @Bean + public MessageChannel distMapChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distMapBulkChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distListChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distSetChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distQueueChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel topicChannel2() { + return new DirectChannel(); + } + + @Bean + public MessageChannel multiMapChannel2() { + return new DirectChannel(); + } + + @Bean + public MessageChannel replicatedMapChannel2() { + return new DirectChannel(); + } + + @Bean + public IMap distMap() { + return testHzInstance().getMap("Distributed_Map1"); + } + + @Bean + public IMap distBulkMap() { + return testHzInstance().getMap("Distributed_Bulk_Map1"); + } + + @Bean + public List distList() { + return testHzInstance().getList("Distributed_List1"); + } + + @Bean + public Set distSet() { + return testHzInstance().getSet("Distributed_Set1"); + } + + @Bean + public Queue distQueue() { + return testHzInstance().getQueue("Distributed_Queue1"); + } + + @Bean + public ITopic topic() { + return testHzInstance().getTopic("Topic1"); + } + + @Bean + public MultiMap multiMap() { + return testHzInstance().getMultiMap("Multi_Map1"); + } + + @Bean + public ReplicatedMap replicatedMap() { + return testHzInstance().getReplicatedMap("Replicated_Map1"); + } + + @Bean(destroyMethod = "") + public HazelcastInstance testHzInstance() { + return Hazelcast.newHazelcastInstance(); + } + + @Bean + public HazelcastTestRequestHandlerAdvice distMapRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice distBulkMapRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice(1); + } + + @Bean + public HazelcastTestRequestHandlerAdvice distListRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice distSetRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice distQueueRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice topicRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice multiMapRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + public HazelcastTestRequestHandlerAdvice replicatedMapRequestHandlerAdvice() { + return new HazelcastTestRequestHandlerAdvice( + HazelcastOutboundChannelAdapterTestUtils.DATA_COUNT); + } + + @Bean + @ServiceActivator(inputChannel = "distMapChannel", adviceChain = "distMapRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject(distMap()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distMapBulkChannel", + adviceChain = "distBulkMapRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler2() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject(distBulkMap()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distListChannel", + adviceChain = "distListRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler3() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject((DistributedObject) distList()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distSetChannel", + adviceChain = "distSetRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler4() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject((DistributedObject) distSet()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distQueueChannel", + adviceChain = "distQueueRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler5() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject((DistributedObject) distQueue()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "topicChannel2", + adviceChain = "topicRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler6() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject(topic()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "multiMapChannel2", + adviceChain = "multiMapRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler7() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject(multiMap()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "replicatedMapChannel2", + adviceChain = "replicatedMapRequestHandlerAdvice") + public HazelcastCacheWritingMessageHandler hazelcastCacheWritingMessageHandler8() { + final HazelcastCacheWritingMessageHandler handler = + new HazelcastCacheWritingMessageHandler(); + handler.setDistributedObject(replicatedMap()); + handler + .setKeyExpression(new SpelExpressionParser().parseExpression("payload.id")); + handler.setExtractPayload(true); + + return handler; + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java new file mode 100644 index 0000000000..1afaaa1eef --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java @@ -0,0 +1,204 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.outbound.config; + +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvice; +import org.springframework.integration.hazelcast.outbound.util.HazelcastOutboundChannelAdapterTestUtils; +import org.springframework.messaging.MessageChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.hazelcast.instance.impl.HazelcastInstanceFactory; +import com.hazelcast.map.IMap; +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; + +/** + * Hazelcast Outbound Channel Adapter JavaConfig driven Unit Test Class + * + * @author Eren Avsarogullari + * @since 6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = HazelcastIntegrationOutboundTestConfiguration.class, + loader = AnnotationConfigContextLoader.class) +@DirtiesContext +public class HazelcastOutboundChannelAdapterConfigTests { + + @Autowired + @Qualifier("distMapChannel") + private MessageChannel distMapChannel; + + @Autowired + @Qualifier("distMapBulkChannel") + private MessageChannel distMapBulkChannel; + + @Autowired + @Qualifier("distListChannel") + private MessageChannel distListChannel; + + @Autowired + @Qualifier("distSetChannel") + private MessageChannel distSetChannel; + + @Autowired + @Qualifier("distQueueChannel") + private MessageChannel distQueueChannel; + + @Autowired + @Qualifier("topicChannel2") + private MessageChannel topicChannel2; + + @Autowired + @Qualifier("multiMapChannel2") + private MessageChannel multiMapChannel2; + + @Autowired + @Qualifier("replicatedMapChannel2") + private MessageChannel replicatedMapChannel2; + + @Autowired + private IMap distMap; + + @Autowired + private IMap distBulkMap; + + @Autowired + private List distList; + + @Autowired + private Set distSet; + + @Autowired + private Queue distQueue; + + @Autowired + private ITopic topic; + + @Autowired + private MultiMap multiMap; + + @Autowired + private ReplicatedMap replicatedMap; + + @Autowired + @Qualifier("distMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice distMapRequestHandlerAdvice; + + @Autowired + @Qualifier("distBulkMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice distBulkMapRequestHandlerAdvice; + + @Autowired + @Qualifier("distListRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice distListRequestHandlerAdvice; + + @Autowired + @Qualifier("distSetRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice distSetRequestHandlerAdvice; + + @Autowired + @Qualifier("distQueueRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice distQueueRequestHandlerAdvice; + + @Autowired + @Qualifier("topicRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice topicRequestHandlerAdvice; + + @Autowired + @Qualifier("multiMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice multiMapRequestHandlerAdvice; + + @Autowired + @Qualifier("replicatedMapRequestHandlerAdvice") + private HazelcastTestRequestHandlerAdvice replicatedMapRequestHandlerAdvice; + + @AfterClass + public static void shutdown() { + HazelcastInstanceFactory.terminateAll(); + } + + @Test + public void testWriteToDistributedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedMap(this.distMapChannel, this.distMap, + this.distMapRequestHandlerAdvice); + } + + @Test + public void testBulkWriteToDistributedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testBulkWriteToDistributedMap(this.distMapBulkChannel, this.distBulkMap, + this.distBulkMapRequestHandlerAdvice); + } + + @Test + public void testWriteToDistributedList() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedList(this.distListChannel, this.distList, + this.distListRequestHandlerAdvice); + } + + @Test + public void testWriteToDistributedSet() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedSet(this.distSetChannel, this.distSet, + this.distSetRequestHandlerAdvice); + } + + @Test + public void testWriteToDistributedQueue() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToDistributedQueue(this.distQueueChannel, this.distQueue, + this.distQueueRequestHandlerAdvice); + } + + @Test + public void testWriteToTopic() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToTopic(this.topicChannel2, this.topic, this.topicRequestHandlerAdvice); + } + + @Test + public void testWriteToMultiMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToMultiMap(this.multiMapChannel2, this.multiMap, + this.multiMapRequestHandlerAdvice); + } + + @Test + public void testWriteToReplicatedMap() { + HazelcastOutboundChannelAdapterTestUtils + .testWriteToReplicatedMap(this.replicatedMapChannel2, this.replicatedMap, + this.replicatedMapRequestHandlerAdvice); + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/util/HazelcastOutboundChannelAdapterTestUtils.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/util/HazelcastOutboundChannelAdapterTestUtils.java new file mode 100644 index 0000000000..0905e57423 --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/util/HazelcastOutboundChannelAdapterTestUtils.java @@ -0,0 +1,248 @@ +/* + * Copyright 2016-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.outbound.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; + +import org.springframework.integration.hazelcast.HazelcastIntegrationTestUser; +import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvice; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.GenericMessage; + +import com.hazelcast.multimap.MultiMap; +import com.hazelcast.replicatedmap.ReplicatedMap; +import com.hazelcast.topic.ITopic; +import com.hazelcast.topic.MessageListener; + +/** + * Util Class for Hazelcast Outbound Channel Adapter Test Support + * + * @author Eren Avsarogullari + * + * @since 6.0 + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public final class HazelcastOutboundChannelAdapterTestUtils { + + public static final int DATA_COUNT = 100; + + public static final int DEFAULT_AGE = 5; + + public static final String TEST_NAME = "Test_Name"; + + public static final String TEST_SURNAME = "Test_Surname"; + + public static void testWriteToDistributedMap(MessageChannel channel, + Map distributedMap, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + testWriteToMap(channel, distributedMap, requestHandlerAdvice); + } + + private static void testWriteToMap(MessageChannel channel, + Map distributedMap, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + sendMessageToChannel(channel); + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + verifyMapForPayload(new TreeMap(distributedMap)); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + } + + public static void testBulkWriteToDistributedMap(MessageChannel channel, + Map distributedMap, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + Map userMap = + new HashMap<>(DATA_COUNT); + for (int index = 1; index <= DATA_COUNT; index++) { + userMap.put(index, getTestUser(index)); + } + + channel.send(new GenericMessage<>(userMap)); + + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + verifyMapForPayload(new TreeMap(distributedMap)); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + } + + public static void testWriteToMultiMap(MessageChannel channel, + MultiMap multiMap, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + sendMessageToChannel(channel); + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + verifyMultiMapForPayload(multiMap); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + } + + public static void testWriteToReplicatedMap(MessageChannel channel, + ReplicatedMap replicatedMap, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + testWriteToMap(channel, replicatedMap, requestHandlerAdvice); + } + + public static void testWriteToDistributedList(MessageChannel channel, + List distributedList, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + testWriteToDistributedCollection(channel, distributedList, requestHandlerAdvice); + } + + private static void testWriteToDistributedCollection(MessageChannel channel, + Collection distributedList, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + sendMessageToChannel(channel); + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + verifyCollection(distributedList, DATA_COUNT); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + } + + public static void testWriteToDistributedSet(MessageChannel channel, + Set distributedSet, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + sendMessageToChannel(channel); + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + final List list = new ArrayList(distributedSet); + Collections.sort(list); + verifyCollection(list, DATA_COUNT); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + } + + public static void testWriteToDistributedQueue(MessageChannel channel, + Queue distributedQueue, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + testWriteToDistributedCollection(channel, distributedQueue, requestHandlerAdvice); + } + + public static void testWriteToTopic(MessageChannel channel, + ITopic topic, + HazelcastTestRequestHandlerAdvice requestHandlerAdvice) { + try { + topic.addMessageListener(new MessageListener() { + + private int index = 1; + + @Override + public void onMessage(com.hazelcast.topic.Message message) { + HazelcastIntegrationTestUser user = + (HazelcastIntegrationTestUser) message.getMessageObject(); + verifyHazelcastIntegrationTestUser(user, index); + index++; + } + }); + sendMessageToChannel(channel); + assertThat(requestHandlerAdvice.executeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + } + catch (InterruptedException e) { + fail("Test has been failed due to " + e.getMessage()); + } + + } + + public static HazelcastIntegrationTestUser getTestUser(int index) { + return new HazelcastIntegrationTestUser(index, TEST_NAME, TEST_SURNAME, + index + DEFAULT_AGE); + } + + public static void verifyMapForPayload( + final Map map) { + int index = 1; + assertThat(map).isNotNull(); + assertThat(map.size() == DATA_COUNT).isEqualTo(true); + for (Map.Entry entry : map.entrySet()) { + assertThat(entry).isNotNull(); + assertThat(entry.getKey().intValue()).isEqualTo(index); + verifyHazelcastIntegrationTestUser(entry.getValue(), index); + index++; + } + } + + public static void verifyCollection( + final Collection coll, final int dataCount) { + int index = 1; + assertThat(coll).isNotNull(); + assertThat(coll.size() == dataCount).isEqualTo(true); + for (HazelcastIntegrationTestUser user : coll) { + verifyHazelcastIntegrationTestUser(user, index); + index++; + } + } + + public static void verifyHazelcastIntegrationTestUser( + HazelcastIntegrationTestUser user, int index) { + assertThat(user).isNotNull(); + assertThat(user.getId()).isEqualTo(index); + assertThat(user.getName()).isEqualTo(TEST_NAME); + assertThat(user.getSurname()).isEqualTo(TEST_SURNAME); + assertThat(user.getAge()).isEqualTo(index + DEFAULT_AGE); + } + + private static void sendMessageToChannel(final MessageChannel channel) { + for (int index = 1; index <= DATA_COUNT; index++) { + channel.send(new GenericMessage<>(getTestUser(index))); + } + } + + private static void verifyMultiMapForPayload( + final MultiMap multiMap) { + int index = 1; + assertThat(multiMap).isNotNull(); + assertThat(multiMap.size() == DATA_COUNT).isEqualTo(true); + SortedSet keys = new TreeSet<>(multiMap.keySet()); + for (Integer key : keys) { + assertThat(key).isNotNull(); + assertThat(key.intValue()).isEqualTo(index); + HazelcastIntegrationTestUser user = multiMap.get(key).iterator().next(); + verifyHazelcastIntegrationTestUser(user, index); + index++; + } + } + + private HazelcastOutboundChannelAdapterTestUtils() { + } + +} diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/store/HazelcastMessageStoreTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/store/HazelcastMessageStoreTests.java new file mode 100644 index 0000000000..7cc308d40e --- /dev/null +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/store/HazelcastMessageStoreTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2017-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.hazelcast.store; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.history.MessageHistory; +import org.springframework.integration.store.MessageGroup; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.GenericMessage; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; + +/** + * @author Vinicius Carvalho + * @author Artem Bilan + */ +public class HazelcastMessageStoreTests { + + private static HazelcastMessageStore store; + + private static HazelcastInstance instance; + + private static IMap map; + + @BeforeClass + public static void init() { + instance = Hazelcast.newHazelcastInstance(); + map = instance.getMap("customTestsMessageStore"); + store = new HazelcastMessageStore(map); + } + + @AfterClass + public static void destroy() { + instance.getLifecycleService().terminate(); + } + + @Before + public void clean() { + map.clear(); + } + + @Test + public void testWithMessageHistory() { + + Message message = new GenericMessage<>("Hello"); + DirectChannel fooChannel = new DirectChannel(); + fooChannel.setBeanName("fooChannel"); + DirectChannel barChannel = new DirectChannel(); + barChannel.setBeanName("barChannel"); + + message = MessageHistory.write(message, fooChannel); + message = MessageHistory.write(message, barChannel); + store.addMessage(message); + message = store.getMessage(message.getHeaders().getId()); + MessageHistory messageHistory = MessageHistory.read(message); + assertThat(messageHistory).isNotNull(); + assertThat(messageHistory.size()).isEqualTo(2); + Properties fooChannelHistory = messageHistory.get(0); + assertThat(fooChannelHistory.get("name")).isEqualTo("fooChannel"); + assertThat(fooChannelHistory.get("type")).isEqualTo("channel"); + + } + + @Test + public void testAddAndRemoveMessagesFromMessageGroup() { + String groupId = "X"; + List> messages = new ArrayList<>(); + for (int i = 0; i < 25; i++) { + Message message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build(); + store.addMessagesToGroup(groupId, message); + messages.add(message); + } + MessageGroup group = store.getMessageGroup(groupId); + assertThat(group.size()).isEqualTo(25); + store.removeMessagesFromGroup(groupId, messages); + group = store.getMessageGroup(groupId); + assertThat(group.size()).isEqualTo(0); + } + + @Test + public void addAndGetMessage() { + + Message message = MessageBuilder.withPayload("test").build(); + store.addMessage(message); + Message retrieved = store.getMessage(message.getHeaders().getId()); + assertThat(retrieved).isEqualTo(message); + } + + @Test + public void customMap() { + assertThat(TestUtils.getPropertyValue(store, "map")).isSameAs(map); + HazelcastMessageStore store2 = new HazelcastMessageStore(instance); + assertThat(TestUtils.getPropertyValue(store2, "map")).isNotSameAs(map); + } + + @Test + public void messageStoreSize() { + Message message1 = MessageBuilder.withPayload("test").build(); + Message message2 = MessageBuilder.withPayload("test").build(); + store.addMessage(message1); + store.addMessage(message2); + long size = store.getMessageCount(); + assertThat(size).isEqualTo(2); + } + + @Test + public void messageStoreIterator() { + Message message1 = MessageBuilder.withPayload("test").build(); + Message message2 = MessageBuilder.withPayload("test").build(); + store.addMessageToGroup("test", message1); + store.addMessageToGroup("test", message2); + int groupCount = 0; + for (MessageGroup messageGroup : store) { + assertThat(messageGroup.size()).isEqualTo(2); + groupCount++; + } + assertThat(groupCount).isEqualTo(1); + } + +} diff --git a/spring-integration-hazelcast/src/test/resources/log4j2-test.xml b/spring-integration-hazelcast/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..8d9b55201a --- /dev/null +++ b/spring-integration-hazelcast/src/test/resources/log4j2-test.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +