From a52169cc8ac8be2f69838f24889fd844d4a86478 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 19 Nov 2024 10:10:09 +0100 Subject: [PATCH 01/16] Prepare next development iteration. See #3046 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5df70171f..06dbee873e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> - <version>3.5.0-SNAPSHOT</version> + <version>4.0.0-SNAPSHOT</version> <name>Spring Data Redis</name> <description>Spring Data module for Redis</description> From d9871bb67ffadfdd55690800a01ba3f338eb8e8c Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 19 Nov 2024 10:10:10 +0100 Subject: [PATCH 02/16] After release cleanups. See #3046 --- Jenkinsfile | 2 +- pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a4820853f1..b95fd94e19 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,7 @@ pipeline { triggers { pollSCM 'H/10 * * * *' - upstream(upstreamProjects: "spring-data-keyvalue/main", threshold: hudson.model.Result.SUCCESS) + upstream(upstreamProjects: "spring-data-keyvalue/4.0.x", threshold: hudson.model.Result.SUCCESS) } options { diff --git a/pom.xml b/pom.xml index 06dbee873e..8cc4a59358 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ <parent> <groupId>org.springframework.data.build</groupId> <artifactId>spring-data-parent</artifactId> - <version>3.5.0-SNAPSHOT</version> + <version>4.0.0-SNAPSHOT</version> </parent> <properties> - <springdata.keyvalue>3.5.0-SNAPSHOT</springdata.keyvalue> - <springdata.commons>3.5.0-SNAPSHOT</springdata.commons> + <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> + <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> <awaitility>4.0.2</awaitility> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> From ffd5e0643550831a4693cb5e7856201cb75e83d2 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 19 Nov 2024 14:44:40 +0100 Subject: [PATCH 03/16] Adopt to deprecation removals in Commons. Closes #3051 --- .../repository/query/RedisPartTreeQuery.java | 6 +-- ...sitoryConfigurationExtensionUnitTests.java | 39 ++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/repository/query/RedisPartTreeQuery.java b/src/main/java/org/springframework/data/redis/repository/query/RedisPartTreeQuery.java index 5582f36cc4..4b69f8f23d 100644 --- a/src/main/java/org/springframework/data/redis/repository/query/RedisPartTreeQuery.java +++ b/src/main/java/org/springframework/data/redis/repository/query/RedisPartTreeQuery.java @@ -35,9 +35,9 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.util.ReflectionUtils; import org.springframework.data.util.Streamable; @@ -54,9 +54,9 @@ public class RedisPartTreeQuery extends KeyValuePartTreeQuery { private final RedisKeyValueAdapter adapter; - public RedisPartTreeQuery(QueryMethod queryMethod, QueryMethodEvaluationContextProvider evaluationContextProvider, + public RedisPartTreeQuery(QueryMethod queryMethod, ValueExpressionDelegate valueExpressionDelegate, KeyValueOperations template, Class<? extends AbstractQueryCreator<?, ?>> queryCreator) { - super(queryMethod, evaluationContextProvider, template, queryCreator); + super(queryMethod, valueExpressionDelegate, template, queryCreator); this.adapter = (RedisKeyValueAdapter) template.getKeyValueAdapter(); } diff --git a/src/test/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtensionUnitTests.java b/src/test/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtensionUnitTests.java index b515de2ac4..afc66411de 100644 --- a/src/test/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtensionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtensionUnitTests.java @@ -28,6 +28,7 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.data.annotation.Id; import org.springframework.data.keyvalue.repository.KeyValueRepository; @@ -46,12 +47,12 @@ */ class RedisRepositoryConfigurationExtensionUnitTests { - private StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(Config.class, true); + private AnnotationMetadata metadata = AnnotationMetadata.introspect(Config.class); private ResourceLoader loader = new PathMatchingResourcePatternResolver(); private Environment environment = new StandardEnvironment(); private BeanDefinitionRegistry registry = new DefaultListableBeanFactory(); private RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, - EnableRedisRepositories.class, loader, environment, registry); + EnableRedisRepositories.class, loader, environment, registry, null); private RedisRepositoryConfigurationExtension extension = new RedisRepositoryConfigurationExtension(); @@ -75,46 +76,46 @@ void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() { @Test // DATAREDIS-491 void picksUpEnableKeyspaceEventsOnStartupCorrectly() { - metadata = new StandardAnnotationMetadata(Config.class, true); - BeanDefinitionRegistry beanDefintionRegistry = getBeanDefinitionRegistry(); + metadata = AnnotationMetadata.introspect(Config.class); + BeanDefinitionRegistry bdr = getBeanDefinitionRegistry(); - assertThat(getEnableKeyspaceEvents(beanDefintionRegistry)).isEqualTo((Object) EnableKeyspaceEvents.ON_STARTUP); + assertThat(getEnableKeyspaceEvents(bdr)).isEqualTo((Object) EnableKeyspaceEvents.ON_STARTUP); } @Test // DATAREDIS-491 void picksUpEnableKeyspaceEventsDefaultCorrectly() { - metadata = new StandardAnnotationMetadata(ConfigWithKeyspaceEventsDisabled.class, true); - BeanDefinitionRegistry beanDefintionRegistry = getBeanDefinitionRegistry(); + metadata = AnnotationMetadata.introspect(ConfigWithKeyspaceEventsDisabled.class); + BeanDefinitionRegistry bdr = getBeanDefinitionRegistry(); - assertThat(getEnableKeyspaceEvents(beanDefintionRegistry)).isEqualTo((Object) EnableKeyspaceEvents.OFF); + assertThat(getEnableKeyspaceEvents(bdr)).isEqualTo((Object) EnableKeyspaceEvents.OFF); } @Test // DATAREDIS-505 void picksUpDefaultKeyspaceNotificationsConfigParameterCorrectly() { metadata = new StandardAnnotationMetadata(Config.class, true); - BeanDefinitionRegistry beanDefintionRegistry = getBeanDefinitionRegistry(); + BeanDefinitionRegistry bdr = getBeanDefinitionRegistry(); - assertThat(getKeyspaceNotificationsConfigParameter(beanDefintionRegistry)).isEqualTo((Object) "Ex"); + assertThat(getKeyspaceNotificationsConfigParameter(bdr)).isEqualTo((Object) "Ex"); } @Test // DATAREDIS-505 void picksUpCustomKeyspaceNotificationsConfigParameterCorrectly() { metadata = new StandardAnnotationMetadata(ConfigWithKeyspaceEventsEnabledAndCustomEventConfig.class, true); - BeanDefinitionRegistry beanDefintionRegistry = getBeanDefinitionRegistry(); + BeanDefinitionRegistry bdr = getBeanDefinitionRegistry(); - assertThat(getKeyspaceNotificationsConfigParameter(beanDefintionRegistry)).isEqualTo((Object) "KEA"); + assertThat(getKeyspaceNotificationsConfigParameter(bdr)).isEqualTo((Object) "KEA"); } @Test // DATAREDIS-1049 void explicitlyEmptyKeyspaceNotificationsConfigParameterShouldBeCapturedCorrectly() { metadata = new StandardAnnotationMetadata(ConfigWithEmptyConfigParameter.class, true); - BeanDefinitionRegistry beanDefintionRegistry = getBeanDefinitionRegistry(); + BeanDefinitionRegistry bdr = getBeanDefinitionRegistry(); - assertThat(getKeyspaceNotificationsConfigParameter(beanDefintionRegistry)).isEqualTo(""); + assertThat(getKeyspaceNotificationsConfigParameter(bdr)).isEqualTo(""); } private static void assertDoesNotHaveRepo(Class<?> repositoryInterface, @@ -147,7 +148,7 @@ private BeanDefinitionRegistry getBeanDefinitionRegistry() { BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, - EnableRedisRepositories.class, loader, environment, registry); + EnableRedisRepositories.class, loader, environment, registry, null); RedisRepositoryConfigurationExtension extension = new RedisRepositoryConfigurationExtension(); @@ -156,13 +157,13 @@ private BeanDefinitionRegistry getBeanDefinitionRegistry() { return registry; } - private Object getEnableKeyspaceEvents(BeanDefinitionRegistry beanDefintionRegistry) { - return beanDefintionRegistry.getBeanDefinition("redisKeyValueAdapter").getPropertyValues() + private Object getEnableKeyspaceEvents(BeanDefinitionRegistry bdr) { + return bdr.getBeanDefinition("redisKeyValueAdapter").getPropertyValues() .getPropertyValue("enableKeyspaceEvents").getValue(); } - private Object getKeyspaceNotificationsConfigParameter(BeanDefinitionRegistry beanDefintionRegistry) { - return beanDefintionRegistry.getBeanDefinition("redisKeyValueAdapter").getPropertyValues() + private Object getKeyspaceNotificationsConfigParameter(BeanDefinitionRegistry bdr) { + return bdr.getBeanDefinition("redisKeyValueAdapter").getPropertyValues() .getPropertyValue("keyspaceNotificationsConfigParameter").getValue(); } From 6000336ac2fe64fd53bb1f1035fc33a7ffde8e48 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Thu, 16 Jan 2025 11:50:08 +0100 Subject: [PATCH 04/16] Remove `MicrometerTracingAdapter` in favor of Lettuce's Micrometer support. Closes #3093 --- .../modules/ROOT/pages/observability.adoc | 80 +---- .../DefaultLettuceObservationConvention.java | 84 ----- .../LettuceObservationContext.java | 75 ---- .../LettuceObservationConvention.java | 37 -- .../MicrometerTracingAdapter.java | 340 ------------------ .../observability/RedisObservation.java | 175 --------- .../observability/SocketAddressEndpoint.java | 40 --- .../lettuce/observability/package-info.java | 6 - 8 files changed, 5 insertions(+), 832 deletions(-) delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/DefaultLettuceObservationConvention.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationContext.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationConvention.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/MicrometerTracingAdapter.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/RedisObservation.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/SocketAddressEndpoint.java delete mode 100644 src/main/java/org/springframework/data/redis/connection/lettuce/observability/package-info.java diff --git a/src/main/antora/modules/ROOT/pages/observability.adoc b/src/main/antora/modules/ROOT/pages/observability.adoc index e3a43ae122..a663d514fa 100644 --- a/src/main/antora/modules/ROOT/pages/observability.adoc +++ b/src/main/antora/modules/ROOT/pages/observability.adoc @@ -2,7 +2,7 @@ = Observability Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency. -Spring Data Redis ships with a Micrometer integration through the Lettuce driver to collect observations during Redis interaction. +Lettuce ships with a Micrometer integration to collect observations during Redis interaction. Once the integration is set up, Micrometer will create meters and spans (for distributed tracing) for each Redis command. To enable the integration, apply the following configuration to `LettuceClientConfiguration`: @@ -16,7 +16,7 @@ class ObservabilityConfiguration { public ClientResources clientResources(ObservationRegistry observationRegistry) { return ClientResources.builder() - .tracing(new MicrometerTracingAdapter(observationRegistry, "my-redis-cache")) + .tracing(new MicrometerTracing(observationRegistry, "my-redis-cache")) .build(); } @@ -31,77 +31,7 @@ class ObservabilityConfiguration { } ---- -See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#redis[OpenTelemetry Semantic Conventions] for further reference. +See also for further reference: +* https://redis.github.io/lettuce/advanced-usage/#micrometer[Lettuce Tracing] +* https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#redis[OpenTelemetry Semantic Conventions] . -[[observability-metrics]] -== Observability - Metrics - -Below you can find a list of all metrics declared by this project. - -[[observability-metrics-redis-command-observation]] -== Redis Command Observation - -____ -Timer created around a Redis command execution. -____ - -**Metric name** `spring.data.redis`. **Type** `timer` and **base unit** `seconds`. - -Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`. - - - -.Low cardinality Keys -[cols="a,a"] -|=== -|Name | Description -|`db.operation`|Redis command value. -|`db.redis.database_index`|Redis database index. -|`db.system`|Database system. -|`db.user`|Redis user. -|`net.peer.name`|Name of the database host. -|`net.peer.port`|Logical remote port number. -|`net.sock.peer.addr`|Mongo peer address. -|`net.sock.peer.port`|Mongo peer port. -|`net.transport`|Network transport. -|=== - -.High cardinality Keys -[cols="a,a"] -|=== -|Name | Description -|`db.statement`|Redis statement. -|`spring.data.redis.command.error`|Redis error response. -|=== - -[[observability-spans]] -== Observability - Spans - -Below you can find a list of all spans declared by this project. - -[[observability-spans-redis-command-observation]] -== Redis Command Observation Span - -> Timer created around a Redis command execution. - -**Span name** `spring.data.redis`. - -Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`. - - - -.Tag Keys -|=== -|Name | Description -|`db.operation`|Redis command value. -|`db.redis.database_index`|Redis database index. -|`db.statement`|Redis statement. -|`db.system`|Database system. -|`db.user`|Redis user. -|`net.peer.name`|Name of the database host. -|`net.peer.port`|Logical remote port number. -|`net.sock.peer.addr`|Mongo peer address. -|`net.sock.peer.port`|Mongo peer port. -|`net.transport`|Network transport. -|`spring.data.redis.command.error`|Redis error response. -|=== diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/DefaultLettuceObservationConvention.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/DefaultLettuceObservationConvention.java deleted file mode 100644 index ca4fdc65e5..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/DefaultLettuceObservationConvention.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2022-2025 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.data.redis.connection.lettuce.observability; - -import io.lettuce.core.protocol.RedisCommand; -import io.lettuce.core.tracing.Tracing.Endpoint; -import io.micrometer.common.KeyValues; - -import java.net.InetSocketAddress; -import java.util.Locale; - -import org.springframework.data.redis.connection.lettuce.observability.RedisObservation.HighCardinalityCommandKeyNames; -import org.springframework.data.redis.connection.lettuce.observability.RedisObservation.LowCardinalityCommandKeyNames; - -/** - * Default {@link LettuceObservationConvention} implementation. - * - * @author Mark Paluch - * @since 3.0 - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -record DefaultLettuceObservationConvention( - boolean includeCommandArgsInSpanTags) implements LettuceObservationConvention { - - @Override - public KeyValues getLowCardinalityKeyValues(LettuceObservationContext context) { - - Endpoint ep = context.getRequiredEndpoint(); - KeyValues keyValues = KeyValues.of(LowCardinalityCommandKeyNames.DATABASE_SYSTEM.withValue("redis"), // - LowCardinalityCommandKeyNames.REDIS_COMMAND.withValue(context.getRequiredCommand().getType().toString())); - - if (ep instanceof SocketAddressEndpoint endpoint) { - - if (endpoint.socketAddress() instanceof InetSocketAddress inet) { - keyValues = keyValues - .and(KeyValues.of(LowCardinalityCommandKeyNames.NET_SOCK_PEER_ADDR.withValue(inet.getHostString()), - LowCardinalityCommandKeyNames.NET_SOCK_PEER_PORT.withValue("" + inet.getPort()), - LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("IP.TCP"))); - } else { - keyValues = keyValues - .and(KeyValues.of(LowCardinalityCommandKeyNames.NET_PEER_NAME.withValue(endpoint.toString()), - LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("Unix"))); - } - } - - return keyValues; - } - - @Override - public KeyValues getHighCardinalityKeyValues(LettuceObservationContext context) { - - RedisCommand<?, ?, ?> command = context.getRequiredCommand(); - - if (includeCommandArgsInSpanTags) { - - if (command.getArgs() != null) { - return KeyValues.of(HighCardinalityCommandKeyNames.STATEMENT - .withValue(command.getType().toString() + " " + command.getArgs().toCommandString())); - } - } - - return KeyValues.empty(); - } - - @Override - public String getContextualName(LettuceObservationContext context) { - return context.getRequiredCommand().getType().toString().toLowerCase(Locale.ROOT); - } -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationContext.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationContext.java deleted file mode 100644 index eb1321f511..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationContext.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022-2025 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.data.redis.connection.lettuce.observability; - -import io.lettuce.core.protocol.RedisCommand; -import io.lettuce.core.tracing.Tracing.Endpoint; -import io.micrometer.observation.Observation; -import io.micrometer.observation.transport.Kind; -import io.micrometer.observation.transport.SenderContext; - -import org.springframework.lang.Nullable; - -/** - * Micrometer {@link Observation.Context} holding Lettuce contextual details. - * - * @author Mark Paluch - * @since 3.0 - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -public class LettuceObservationContext extends SenderContext<Object> { - - private volatile @Nullable RedisCommand<?, ?, ?> command; - - private volatile @Nullable Endpoint endpoint; - - public LettuceObservationContext(String serviceName) { - super((carrier, key, value) -> {}, Kind.CLIENT); - setRemoteServiceName(serviceName); - } - - public RedisCommand<?, ?, ?> getRequiredCommand() { - - RedisCommand<?, ?, ?> local = command; - - if (local == null) { - throw new IllegalArgumentException("LettuceObservationContext is not associated with a Command"); - } - - return local; - } - - public void setCommand(RedisCommand<?, ?, ?> command) { - this.command = command; - } - - public Endpoint getRequiredEndpoint() { - - Endpoint local = endpoint; - - if (local == null) { - throw new IllegalArgumentException("LettuceObservationContext is not associated with a Endpoint"); - } - - return local; - } - - public void setEndpoint(Endpoint endpoint) { - this.endpoint = endpoint; - } -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationConvention.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationConvention.java deleted file mode 100644 index ea01647152..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/LettuceObservationConvention.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022-2025 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.data.redis.connection.lettuce.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationConvention; - -/** - * {@link ObservationConvention} for {@link LettuceObservationContext}. - * - * @author Mark Paluch - * @since 3.0 - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -interface LettuceObservationConvention extends ObservationConvention<LettuceObservationContext> { - - @Override - default boolean supportsContext(Observation.Context context) { - return context instanceof LettuceObservationContext; - } - -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/MicrometerTracingAdapter.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/MicrometerTracingAdapter.java deleted file mode 100644 index bbf3d28fe6..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/MicrometerTracingAdapter.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2022-2025 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.data.redis.connection.lettuce.observability; - -import io.lettuce.core.protocol.CompleteableCommand; -import io.lettuce.core.protocol.RedisCommand; -import io.lettuce.core.tracing.TraceContext; -import io.lettuce.core.tracing.TraceContextProvider; -import io.lettuce.core.tracing.Tracer; -import io.lettuce.core.tracing.Tracer.Span; -import io.lettuce.core.tracing.TracerProvider; -import io.lettuce.core.tracing.Tracing; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import reactor.core.publisher.Mono; - -import java.net.SocketAddress; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.data.redis.connection.lettuce.observability.RedisObservation.HighCardinalityCommandKeyNames; -import org.springframework.lang.Nullable; - -/** - * {@link Tracing} adapter using Micrometer's {@link Observation}. This adapter integrates with Micrometer to propagate - * observations into timers, distributed traces and any other registered handlers. Observations include a set of tags - * capturing Redis runtime information. - * <h3>Capturing full statements</h3> This adapter can capture full statements when enabling - * {@code includeCommandArgsInSpanTags}. You should carefully consider the impact of this setting as all command - * arguments will be captured in traces including these that may contain sensitive details. - * - * @author Mark Paluch - * @author Yanming Zhou - * @since 3.0 - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -public class MicrometerTracingAdapter implements Tracing { - - private static final Log log = LogFactory.getLog(MicrometerTracingAdapter.class); - - private final ObservationRegistry observationRegistry; - private final String serviceName; - private final boolean includeCommandArgsInSpanTags; - - private final LettuceObservationConvention observationConvention; - - /** - * Create a new {@link MicrometerTracingAdapter} instance. - * - * @param observationRegistry must not be {@literal null}. - * @param serviceName service name to be used. - */ - public MicrometerTracingAdapter(ObservationRegistry observationRegistry, String serviceName) { - this(observationRegistry, serviceName, false); - } - - /** - * Create a new {@link MicrometerTracingAdapter} instance. - * - * @param observationRegistry must not be {@literal null}. - * @param serviceName service name to be used. - * @param includeCommandArgsInSpanTags whether to attach the full command into the trace. Use this flag with caution - * as sensitive arguments will be captured in the observation spans and metric tags. - */ - public MicrometerTracingAdapter(ObservationRegistry observationRegistry, String serviceName, - boolean includeCommandArgsInSpanTags) { - - this.observationRegistry = observationRegistry; - this.serviceName = serviceName; - this.observationConvention = new DefaultLettuceObservationConvention(includeCommandArgsInSpanTags); - this.includeCommandArgsInSpanTags = includeCommandArgsInSpanTags; - } - - @Override - public TracerProvider getTracerProvider() { - return () -> new MicrometerTracer(observationRegistry); - } - - @Override - public TraceContextProvider initialTraceContextProvider() { - return new MicrometerTraceContextProvider(observationRegistry); - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean includeCommandArgsInSpanTags() { - return includeCommandArgsInSpanTags; - } - - @Override - public Endpoint createEndpoint(SocketAddress socketAddress) { - return new SocketAddressEndpoint(socketAddress); - } - - /** - * {@link Tracer} implementation based on Micrometer's {@link ObservationRegistry}. - */ - public class MicrometerTracer extends Tracer { - - private final ObservationRegistry observationRegistry; - - public MicrometerTracer(ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; - } - - @Override - public Tracer.Span nextSpan() { - return this.postProcessSpan(createObservation(null)); - } - - @Override - public Tracer.Span nextSpan(TraceContext traceContext) { - return postProcessSpan(createObservation(traceContext)); - } - - private Observation createObservation(@Nullable TraceContext parentContext) { - - return RedisObservation.REDIS_COMMAND_OBSERVATION.observation(observationRegistry, () -> { - - LettuceObservationContext context = new LettuceObservationContext(serviceName); - - if (parentContext instanceof MicrometerTraceContext traceContext) { - context.setParentObservation(traceContext.observation()); - } - return context; - }); - } - - private Tracer.Span postProcessSpan(Observation observation) { - - return !observation.isNoop() ? new MicrometerSpan(observation.observationConvention(observationConvention)) - : NoOpSpan.INSTANCE; - } - } - - /** - * No-op {@link Span} implementation. - */ - static class NoOpSpan extends Tracer.Span { - - static final NoOpSpan INSTANCE = new NoOpSpan(); - - public NoOpSpan() {} - - @Override - public Tracer.Span start(RedisCommand<?, ?, ?> command) { - return this; - } - - @Override - public Tracer.Span name(String name) { - return this; - } - - @Override - public Tracer.Span annotate(String value) { - return this; - } - - @Override - public Tracer.Span tag(String key, String value) { - return this; - } - - @Override - public Tracer.Span error(Throwable throwable) { - return this; - } - - @Override - public Tracer.Span remoteEndpoint(Tracing.Endpoint endpoint) { - return this; - } - - @Override - public void finish() {} - } - - /** - * Micrometer {@link Observation}-based {@link Span} implementation. - */ - static class MicrometerSpan extends Tracer.Span { - - private final Observation observation; - - private @Nullable RedisCommand<?, ?, ?> command; - - public MicrometerSpan(Observation observation) { - this.observation = observation; - } - - @Override - public Span start(RedisCommand<?, ?, ?> command) { - - ((LettuceObservationContext) observation.getContext()).setCommand(command); - - this.command = command; - - if (log.isDebugEnabled()) { - log.debug("Starting Observation for Command %s".formatted(command)); - } - - if (command instanceof CompleteableCommand<?> completeableCommand) { - - completeableCommand.onComplete((o, throwable) -> { - - if (command.getOutput() != null) { - - String error = command.getOutput().getError(); - if (error != null) { - observation.highCardinalityKeyValue(HighCardinalityCommandKeyNames.ERROR.withValue(error)); - } else if (throwable != null) { - error(throwable); - } - } - - finish(); - }); - } else { - throw new IllegalArgumentException("Command " + command - + " must implement CompleteableCommand to attach Span completion to command completion"); - } - - observation.start(); - return this; - } - - @Override - public Span name(String name) { - return this; - } - - @Override - public Span annotate(String annotation) { - return this; - } - - @Override - public Span tag(String key, String value) { - observation.highCardinalityKeyValue(key, value); - return this; - } - - @Override - public Span error(Throwable throwable) { - - if (log.isDebugEnabled()) { - log.debug("Attaching error to Observation for Command %s".formatted(command)); - } - - observation.error(throwable); - return this; - } - - @Override - public Span remoteEndpoint(Endpoint endpoint) { - - ((LettuceObservationContext) observation.getContext()).setEndpoint(endpoint); - return this; - } - - @Override - public void finish() { - - if (log.isDebugEnabled()) { - log.debug("Stopping Observation for Command %s".formatted(command)); - } - - observation.stop(); - } - } - - /** - * {@link TraceContextProvider} using {@link ObservationRegistry}. - */ - record MicrometerTraceContextProvider(ObservationRegistry registry) implements TraceContextProvider { - - @Override - @Nullable - public TraceContext getTraceContext() { - - Observation observation = registry.getCurrentObservation(); - - if (observation == null) { - return null; - } - - return new MicrometerTraceContext(observation); - } - - @Override - public Mono<TraceContext> getTraceContextLater() { - - return Mono.deferContextual(Mono::justOrEmpty).filter((it) -> { - return it.hasKey(TraceContext.class) || it.hasKey(Observation.class) - || it.hasKey(ObservationThreadLocalAccessor.KEY); - }).map((it) -> { - - if (it.hasKey(Observation.class)) { - return new MicrometerTraceContext(it.get(Observation.class)); - } - - if (it.hasKey(TraceContext.class)) { - return it.get(TraceContext.class); - } - - return new MicrometerTraceContext(it.get(ObservationThreadLocalAccessor.KEY)); - }); - } - } - - /** - * {@link TraceContext} implementation using {@link Observation}. - * - * @param observation - */ - record MicrometerTraceContext(Observation observation) implements TraceContext { - - } -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/RedisObservation.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/RedisObservation.java deleted file mode 100644 index 989f2cece8..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/RedisObservation.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2013-2025 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.data.redis.connection.lettuce.observability; - -import io.micrometer.common.docs.KeyName; -import io.micrometer.observation.docs.ObservationDocumentation; - -/** - * A Redis-based {@link io.micrometer.observation.Observation}. - * - * @author Mark Paluch - * @since 3.0 - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -public enum RedisObservation implements ObservationDocumentation { - - /** - * Timer created around a Redis command execution. - */ - REDIS_COMMAND_OBSERVATION { - - @Override - public String getName() { - return "spring.data.redis"; - } - - @Override - public KeyName[] getLowCardinalityKeyNames() { - return LowCardinalityCommandKeyNames.values(); - } - - @Override - public KeyName[] getHighCardinalityKeyNames() { - return HighCardinalityCommandKeyNames.values(); - } - }; - - /** - * Enums related to low cardinality key names for Redis commands. - */ - enum LowCardinalityCommandKeyNames implements KeyName { - - /** - * Database system. - */ - DATABASE_SYSTEM { - @Override - public String asString() { - return "db.system"; - } - }, - - /** - * Network transport. - */ - NET_TRANSPORT { - @Override - public String asString() { - return "net.transport"; - } - }, - - /** - * Name of the database host. - */ - NET_PEER_NAME { - @Override - public String asString() { - return "net.peer.name"; - } - }, - - /** - * Logical remote port number. - */ - NET_PEER_PORT { - @Override - public String asString() { - return "net.peer.port"; - } - }, - - /** - * Mongo peer address. - */ - NET_SOCK_PEER_ADDR { - @Override - public String asString() { - return "net.sock.peer.addr"; - } - }, - - /** - * Mongo peer port. - */ - NET_SOCK_PEER_PORT { - @Override - public String asString() { - return "net.sock.peer.port"; - } - }, - - /** - * Redis user. - */ - DB_USER { - @Override - public String asString() { - return "db.user"; - } - }, - - /** - * Redis database index. - */ - DB_INDEX { - @Override - public String asString() { - return "db.redis.database_index"; - } - }, - - /** - * Redis command value. - */ - REDIS_COMMAND { - @Override - public String asString() { - return "db.operation"; - } - } - - } - - /** - * Enums related to high cardinality key names for Redis commands. - */ - enum HighCardinalityCommandKeyNames implements KeyName { - - /** - * Redis statement. - */ - STATEMENT { - @Override - public String asString() { - return "db.statement"; - } - }, - - /** - * Redis error response. - */ - ERROR { - @Override - public String asString() { - return "spring.data.redis.command.error"; - } - } - } -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/SocketAddressEndpoint.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/SocketAddressEndpoint.java deleted file mode 100644 index fdaf1c27e1..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/SocketAddressEndpoint.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022-2025 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.data.redis.connection.lettuce.observability; - -import io.lettuce.core.tracing.Tracing.Endpoint; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -/** - * @author Mark Paluch - * @deprecated since 3.4 for removal with the next major revision. Use Lettuce's Micrometer integration through - * {@link io.lettuce.core.tracing.MicrometerTracing}. - */ -@Deprecated(since = "3.4", forRemoval = true) -record SocketAddressEndpoint(SocketAddress socketAddress) implements Endpoint { - - @Override - public String toString() { - - if (socketAddress instanceof InetSocketAddress inet) { - return inet.getHostString() + ":" + inet.getPort(); - } - - return socketAddress.toString(); - } -} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/package-info.java b/src/main/java/org/springframework/data/redis/connection/lettuce/observability/package-info.java deleted file mode 100644 index e3231ef4c3..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/observability/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Integration of Micrometer Tracing for Lettuce Observability. - */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields -package org.springframework.data.redis.connection.lettuce.observability; From 71cc768d484549cd787db948bbdf28a2a6053955 Mon Sep 17 00:00:00 2001 From: Christoph Strobl <christoph.strobl@broadcom.com> Date: Fri, 24 Jan 2025 10:48:00 +0100 Subject: [PATCH 05/16] Prepare 4.0 M1 (2025.1.0). See #3046 --- pom.xml | 22 ++++---------- src/main/resources/notice.txt | 54 +---------------------------------- 2 files changed, 6 insertions(+), 70 deletions(-) diff --git a/pom.xml b/pom.xml index 8cc4a59358..30f1b4b6a5 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ <parent> <groupId>org.springframework.data.build</groupId> <artifactId>spring-data-parent</artifactId> - <version>4.0.0-SNAPSHOT</version> + <version>4.0.0-M1</version> </parent> <properties> - <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> - <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> + <springdata.keyvalue>4.0.0-M1</springdata.keyvalue> + <springdata.commons>4.0.0-M1</springdata.commons> <awaitility>4.0.2</awaitility> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> @@ -389,19 +389,7 @@ </profiles> <repositories> - <repository> - <id>spring-snapshot</id> - <url>https://repo.spring.io/snapshot</url> - <snapshots> - <enabled>true</enabled> - </snapshots> - <releases> - <enabled>false</enabled> - </releases> - </repository> - <repository> - <id>spring-milestone</id> - <url>https://repo.spring.io/milestone</url> - </repository> + + </repositories> </project> diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 880bae215e..228ee7b1df 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Redis 3.5 M2 (2025.0.0) +Spring Data Redis 4.0 M1 (2025.1.0) Copyright (c) [2010-2019] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -8,55 +8,3 @@ This product may include a number of subcomponents with separate copyright notices and license terms. Your use of the source code for the these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 37e5865f986c12abfaacd984518b2c90cb60275b Mon Sep 17 00:00:00 2001 From: Christoph Strobl <christoph.strobl@broadcom.com> Date: Fri, 24 Jan 2025 10:48:55 +0100 Subject: [PATCH 06/16] Release version 4.0 M1 (2025.1.0). See #3046 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30f1b4b6a5..7d1412ffef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> - <version>4.0.0-SNAPSHOT</version> + <version>4.0.0-M1</version> <name>Spring Data Redis</name> <description>Spring Data module for Redis</description> From aed9c5b3ac41b913d06f9340c48edde40192e2a8 Mon Sep 17 00:00:00 2001 From: Christoph Strobl <christoph.strobl@broadcom.com> Date: Fri, 24 Jan 2025 10:53:10 +0100 Subject: [PATCH 07/16] Prepare next development iteration. See #3046 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7d1412ffef..30f1b4b6a5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> - <version>4.0.0-M1</version> + <version>4.0.0-SNAPSHOT</version> <name>Spring Data Redis</name> <description>Spring Data module for Redis</description> From 61c22ad789cadf954644de1e6a8997a3b4f165da Mon Sep 17 00:00:00 2001 From: Christoph Strobl <christoph.strobl@broadcom.com> Date: Fri, 24 Jan 2025 10:53:12 +0100 Subject: [PATCH 08/16] After release cleanups. See #3046 --- pom.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 30f1b4b6a5..8cc4a59358 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ <parent> <groupId>org.springframework.data.build</groupId> <artifactId>spring-data-parent</artifactId> - <version>4.0.0-M1</version> + <version>4.0.0-SNAPSHOT</version> </parent> <properties> - <springdata.keyvalue>4.0.0-M1</springdata.keyvalue> - <springdata.commons>4.0.0-M1</springdata.commons> + <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> + <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> <awaitility>4.0.2</awaitility> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> @@ -389,7 +389,19 @@ </profiles> <repositories> - - + <repository> + <id>spring-snapshot</id> + <url>https://repo.spring.io/snapshot</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + <releases> + <enabled>false</enabled> + </releases> + </repository> + <repository> + <id>spring-milestone</id> + <url>https://repo.spring.io/milestone</url> + </repository> </repositories> </project> From 9bc269749b2610c1cae22308246a6df738820d6c Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Fri, 14 Mar 2025 10:20:28 +0100 Subject: [PATCH 09/16] Adopt to changes in KeyValue. --- .../springframework/data/redis/core/RedisKeyValueAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java index ea74c8fc5c..6c49e739d8 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java @@ -346,12 +346,12 @@ public <T> T delete(Object id, String keyspace, Class<T> type) { } @Override - public List<?> getAllOf(String keyspace) { + public List<Object> getAllOf(String keyspace) { return getAllOf(keyspace, Object.class, -1, -1); } @Override - public <T> Iterable<T> getAllOf(String keyspace, Class<T> type) { + public <T> List<T> getAllOf(String keyspace, Class<T> type) { return getAllOf(keyspace, type, -1, -1); } From 6a491e8813d90d88bb9ce129bfc2f0ebd6f0b6fc Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Thu, 17 Apr 2025 09:45:12 +0200 Subject: [PATCH 10/16] Remove custom `awaitility` in favor of Build-managed versions. Closes #3132 --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8cc4a59358..af617ef8f0 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,6 @@ <properties> <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> - <awaitility>4.0.2</awaitility> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> <pool>2.11.1</pool> From 3f28ae3f64cddcab5d0472f126e37aca9e48ef23 Mon Sep 17 00:00:00 2001 From: jonghoonpark <dev@jonghoonpark.com> Date: Wed, 16 Apr 2025 14:39:40 +0900 Subject: [PATCH 11/16] Refine `Topic` creation for `ChannelTopic` and `PatternTopic`. Signed-off-by: jonghoonpark <dev@jonghoonpark.com> Closes #3131 --- src/main/antora/modules/ROOT/pages/redis/pubsub.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc b/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc index 6539ca127c..736683835a 100644 --- a/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc +++ b/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc @@ -162,7 +162,7 @@ XML:: ---- ====== -NOTE: The listener topic can be either a channel (for example, `topic="chatroom"`) or a pattern (for example, `topic="*room"`) +NOTE: The listener topic can be either a channel (for example, `topic="chatroom"`) or a pattern (for example, `topic="*room"`). For channels, you should use the `ChannelTopic` class, and for patterns, use the `PatternTopic` class. The preceding example uses the Redis namespace to declare the message listener container and automatically register the POJOs as listeners. The full-blown beans definition follows: From 148ddfb8e273cc23bb5c53e2c855b9025eb81a71 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Thu, 17 Apr 2025 15:51:02 +0200 Subject: [PATCH 12/16] Refine `Topic` creation for `ChannelTopic` and `PatternTopic`. We now expose factory methods to construct ChannelTopic and PatternTopic from the Topic interface. See #3131 --- .../modules/ROOT/pages/redis/pubsub.adoc | 2 +- .../data/redis/listener/Topic.java | 24 +++++++++++++++++++ ...sageListenerContainerIntegrationTests.java | 13 ++++------ ...edisMessageListenerContainerUnitTests.java | 22 ++++++++--------- ...ctiveRedisOperationsExtensionsUnitTests.kt | 6 ++--- 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc b/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc index 736683835a..031425a4ed 100644 --- a/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc +++ b/src/main/antora/modules/ROOT/pages/redis/pubsub.adoc @@ -162,7 +162,7 @@ XML:: ---- ====== -NOTE: The listener topic can be either a channel (for example, `topic="chatroom"`) or a pattern (for example, `topic="*room"`). For channels, you should use the `ChannelTopic` class, and for patterns, use the `PatternTopic` class. +NOTE: The listener topic can be either a channel (for example, `topic="chatroom"` respective `Topic.channel("chatroom")`) or a pattern (for example, `topic="*room"` respective `Topic.pattern("*room")`). The preceding example uses the Redis namespace to declare the message listener container and automatically register the POJOs as listeners. The full-blown beans definition follows: diff --git a/src/main/java/org/springframework/data/redis/listener/Topic.java b/src/main/java/org/springframework/data/redis/listener/Topic.java index 0aad48207a..bec4cd3c32 100644 --- a/src/main/java/org/springframework/data/redis/listener/Topic.java +++ b/src/main/java/org/springframework/data/redis/listener/Topic.java @@ -19,13 +19,37 @@ * Topic for a Redis message. Acts a high-level abstraction on top of Redis low-level channels or patterns. * * @author Costin Leau + * @author Mark Paluch */ public interface Topic { + /** + * Create a new {@link ChannelTopic} for channel subscriptions. + * + * @param channelName {@link String name} of the Redis channel; must not be {@literal null}. + * @return the {@link ChannelTopic} for the given {@code channelName}. + * @since 3.5 + */ + static ChannelTopic channel(String channelName) { + return ChannelTopic.of(channelName); + } + + /** + * Create a new {@link PatternTopic} for channel subscriptions based on a {@code pattern}. + * + * @param pattern {@link String pattern} used to match channels; must not be {@literal null} or empty. + * @return the {@link PatternTopic} for the given {@code pattern}. + * @since 3.5 + */ + static PatternTopic pattern(String pattern) { + return PatternTopic.of(pattern); + } + /** * Returns the topic (as a String). * * @return the topic */ String getTopic(); + } diff --git a/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerIntegrationTests.java b/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerIntegrationTests.java index e990cea5f2..4b59bc872d 100644 --- a/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerIntegrationTests.java @@ -23,12 +23,9 @@ import java.nio.ByteBuffer; import java.time.Duration; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.List; -import java.util.Queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingDeque; @@ -110,7 +107,7 @@ void shouldReceiveChannelMessages() { ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(connectionFactory); - container.receiveLater(ChannelTopic.of(CHANNEL1)) // + container.receiveLater(Topic.channel(CHANNEL1)) // .doOnNext(it -> doPublish(CHANNEL1.getBytes(), MESSAGE.getBytes())) // .flatMapMany(Function.identity()) // .as(StepVerifier::create) // @@ -153,7 +150,7 @@ public void onChannelUnsubscribed(byte[] channel, long count) { } }; - container.receive(Collections.singletonList(ChannelTopic.of(CHANNEL1)), listener) // + container.receive(Collections.singletonList(Topic.channel(CHANNEL1)), listener) // .as(StepVerifier::create) // .then(awaitSubscription(container::getActiveSubscriptions)) .then(() -> doPublish(CHANNEL1.getBytes(), MESSAGE.getBytes())) // @@ -220,7 +217,7 @@ public void onPatternUnsubscribed(byte[] pattern, long count) { } }; - container.receive(Collections.singletonList(PatternTopic.of(PATTERN1)), listener) // + container.receive(Collections.singletonList(Topic.pattern(PATTERN1)), listener) // .cast(PatternMessage.class) // .as(StepVerifier::create) // .then(awaitSubscription(container::getActiveSubscriptions)) @@ -314,10 +311,10 @@ void multipleListenShouldTrackSubscriptions() throws Exception { ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(connectionFactory); - Flux<? extends ReactiveSubscription.Message<String, String>> c1 = container.receiveLater(ChannelTopic.of(CHANNEL1)) + Flux<? extends ReactiveSubscription.Message<String, String>> c1 = container.receiveLater(Topic.channel(CHANNEL1)) .block(); Flux<? extends ReactiveSubscription.Message<String, String>> c1p1 = container - .receiveLater(Arrays.asList(ChannelTopic.of(CHANNEL1), PatternTopic.of(PATTERN1)), + .receiveLater(Arrays.asList(Topic.channel(CHANNEL1), PatternTopic.of(PATTERN1)), SerializationPair.fromSerializer(RedisSerializer.string()), SerializationPair.fromSerializer(RedisSerializer.string())) .block(); diff --git a/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerUnitTests.java b/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerUnitTests.java index f5e99bff47..f22ff8e560 100644 --- a/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerUnitTests.java +++ b/src/test/java/org/springframework/data/redis/listener/ReactiveRedisMessageListenerContainerUnitTests.java @@ -79,7 +79,7 @@ void shouldSubscribeToPattern() { container = createContainer(); - container.receive(PatternTopic.of("foo*")).as(StepVerifier::create).thenAwait().thenCancel().verify(); + container.receive(Topic.pattern("foo*")).as(StepVerifier::create).thenAwait().thenCancel().verify(); verify(subscriptionMock).pSubscribe(getByteBuffer("foo*")); } @@ -90,7 +90,7 @@ void shouldSubscribeToMultiplePatterns() { when(subscriptionMock.receive()).thenReturn(Flux.never()); container = createContainer(); - container.receive(PatternTopic.of("foo*"), PatternTopic.of("bar*")).as(StepVerifier::create).thenRequest(1) + container.receive(Topic.pattern("foo*"), Topic.pattern("bar*")).as(StepVerifier::create).thenRequest(1) .thenAwait().thenCancel().verify(); verify(subscriptionMock).pSubscribe(getByteBuffer("foo*"), getByteBuffer("bar*")); @@ -102,7 +102,7 @@ void shouldSubscribeToChannel() { when(subscriptionMock.receive()).thenReturn(Flux.never()); container = createContainer(); - container.receive(ChannelTopic.of("foo")).as(StepVerifier::create).thenAwait().thenCancel().verify(); + container.receive(Topic.channel("foo")).as(StepVerifier::create).thenAwait().thenCancel().verify(); verify(subscriptionMock).subscribe(getByteBuffer("foo")); } @@ -113,7 +113,7 @@ void shouldSubscribeToMultipleChannels() { when(subscriptionMock.receive()).thenReturn(Flux.never()); container = createContainer(); - container.receive(ChannelTopic.of("foo"), ChannelTopic.of("bar")).as(StepVerifier::create).thenAwait().thenCancel() + container.receive(Topic.channel("foo"), Topic.channel("bar")).as(StepVerifier::create).thenAwait().thenCancel() .verify(); verify(subscriptionMock).subscribe(getByteBuffer("foo"), getByteBuffer("bar")); @@ -127,7 +127,7 @@ void shouldEmitChannelMessage() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<Message<String, String>> messageStream = container.receive(ChannelTopic.of("foo")); + Flux<Message<String, String>> messageStream = container.receive(Topic.channel("foo")); messageStream.as(StepVerifier::create).then(() -> { sink.tryEmitNext(createChannelMessage("foo", "message")); @@ -146,7 +146,7 @@ void shouldEmitPatternMessage() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<PatternMessage<String, String, String>> messageStream = container.receive(PatternTopic.of("foo*")); + Flux<PatternMessage<String, String, String>> messageStream = container.receive(Topic.pattern("foo*")); messageStream.as(StepVerifier::create).then(() -> { sink.tryEmitNext(createPatternMessage("foo*", "foo", "message")); @@ -171,7 +171,7 @@ void shouldRegisterSubscription() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<Message<String, String>> messageStream = container.receive(ChannelTopic.of("foo*")); + Flux<Message<String, String>> messageStream = container.receive(Topic.channel("foo*")); Disposable subscription = messageStream.subscribe(); @@ -193,7 +193,7 @@ void shouldRegisterSubscriptionMultipleSubscribers() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<Message<String, String>> messageStream = container.receive(new ChannelTopic("foo*")); + Flux<Message<String, String>> messageStream = container.receive(Topic.channel("foo*")); Disposable first = messageStream.subscribe(); Disposable second = messageStream.subscribe(); @@ -216,7 +216,7 @@ void shouldUnsubscribeOnCancel() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<PatternMessage<String, String, String>> messageStream = container.receive(PatternTopic.of("foo*")); + Flux<PatternMessage<String, String, String>> messageStream = container.receive(Topic.pattern("foo*")); messageStream.as(StepVerifier::create).then(() -> { @@ -240,7 +240,7 @@ void shouldTerminateSubscriptionsOnShutdown() { })); container = createContainer(); - Flux<PatternMessage<String, String, String>> messageStream = container.receive(PatternTopic.of("foo*")); + Flux<PatternMessage<String, String, String>> messageStream = container.receive(Topic.pattern("foo*")); messageStream.as(StepVerifier::create).then(() -> { container.destroy(); @@ -255,7 +255,7 @@ void shouldCleanupDownstream() { when(subscriptionMock.receive()).thenReturn(sink.asFlux()); container = createContainer(); - Flux<PatternMessage<String, String, String>> messageStream = container.receive(PatternTopic.of("foo*")); + Flux<PatternMessage<String, String, String>> messageStream = container.receive(Topic.pattern("foo*")); messageStream.as(StepVerifier::create).then(() -> { assertThat(sink.currentSubscriberCount()).isGreaterThan(0); diff --git a/src/test/kotlin/org/springframework/data/redis/core/ReactiveRedisOperationsExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/redis/core/ReactiveRedisOperationsExtensionsUnitTests.kt index 62a6c968f3..90563f411f 100644 --- a/src/test/kotlin/org/springframework/data/redis/core/ReactiveRedisOperationsExtensionsUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/redis/core/ReactiveRedisOperationsExtensionsUnitTests.kt @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test import org.springframework.data.redis.connection.DataType import org.springframework.data.redis.connection.ReactiveSubscription import org.springframework.data.redis.core.script.RedisScript -import org.springframework.data.redis.listener.ChannelTopic +import org.springframework.data.redis.listener.Topic import org.springframework.data.redis.serializer.RedisElementReader import org.springframework.data.redis.serializer.RedisElementWriter import reactor.core.publisher.Flux @@ -167,8 +167,8 @@ class ReactiveRedisOperationsExtensionsUnitTests { @Test // DATAREDIS-1033 fun listenTo() { - val topic1 = ChannelTopic.of("foo") - val topic2 = ChannelTopic.of("bar") + val topic1 = Topic.channel("foo") + val topic2 = Topic.channel("bar") val message = ReactiveSubscription.ChannelMessage("a", "b") val operations = mockk<ReactiveRedisOperations<String, String>>() every { operations.listenTo(any(), any()) } returns Flux.just(message) From 636e4202efe1d0207e1091e880a1c8fb526bb9a3 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 22 Apr 2025 14:29:21 +0200 Subject: [PATCH 13/16] Prepare 4.0 M2 (2025.1.0). See #3097 --- pom.xml | 22 +++++----------------- src/main/resources/notice.txt | 3 ++- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index af617ef8f0..0c2525a85d 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ <parent> <groupId>org.springframework.data.build</groupId> <artifactId>spring-data-parent</artifactId> - <version>4.0.0-SNAPSHOT</version> + <version>4.0.0-M2</version> </parent> <properties> - <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> - <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> + <springdata.keyvalue>4.0.0-M2</springdata.keyvalue> + <springdata.commons>4.0.0-M2</springdata.commons> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> <pool>2.11.1</pool> @@ -388,19 +388,7 @@ </profiles> <repositories> - <repository> - <id>spring-snapshot</id> - <url>https://repo.spring.io/snapshot</url> - <snapshots> - <enabled>true</enabled> - </snapshots> - <releases> - <enabled>false</enabled> - </releases> - </repository> - <repository> - <id>spring-milestone</id> - <url>https://repo.spring.io/milestone</url> - </repository> + + </repositories> </project> diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 228ee7b1df..9b80042f87 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Redis 4.0 M1 (2025.1.0) +Spring Data Redis 4.0 M2 (2025.1.0) Copyright (c) [2010-2019] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -8,3 +8,4 @@ This product may include a number of subcomponents with separate copyright notices and license terms. Your use of the source code for the these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. + From 6efba93b463f5b1e0089710534288ccf47e676a7 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 22 Apr 2025 14:29:39 +0200 Subject: [PATCH 14/16] Release version 4.0 M2 (2025.1.0). See #3097 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0c2525a85d..499836c337 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> - <version>4.0.0-SNAPSHOT</version> + <version>4.0.0-M2</version> <name>Spring Data Redis</name> <description>Spring Data module for Redis</description> From 292b22d33b93252c3e57f3d07314b7631b2dd3af Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 22 Apr 2025 14:32:03 +0200 Subject: [PATCH 15/16] Prepare next development iteration. See #3097 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 499836c337..0c2525a85d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> - <version>4.0.0-M2</version> + <version>4.0.0-SNAPSHOT</version> <name>Spring Data Redis</name> <description>Spring Data module for Redis</description> From 514051be4de4b3c435581f7006ea5407a14c2840 Mon Sep 17 00:00:00 2001 From: Mark Paluch <mark.paluch@broadcom.com> Date: Tue, 22 Apr 2025 14:32:04 +0200 Subject: [PATCH 16/16] After release cleanups. See #3097 --- pom.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0c2525a85d..af617ef8f0 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ <parent> <groupId>org.springframework.data.build</groupId> <artifactId>spring-data-parent</artifactId> - <version>4.0.0-M2</version> + <version>4.0.0-SNAPSHOT</version> </parent> <properties> - <springdata.keyvalue>4.0.0-M2</springdata.keyvalue> - <springdata.commons>4.0.0-M2</springdata.commons> + <springdata.keyvalue>4.0.0-SNAPSHOT</springdata.keyvalue> + <springdata.commons>4.0.0-SNAPSHOT</springdata.commons> <beanutils>1.9.4</beanutils> <xstream>1.4.21</xstream> <pool>2.11.1</pool> @@ -388,7 +388,19 @@ </profiles> <repositories> - - + <repository> + <id>spring-snapshot</id> + <url>https://repo.spring.io/snapshot</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + <releases> + <enabled>false</enabled> + </releases> + </repository> + <repository> + <id>spring-milestone</id> + <url>https://repo.spring.io/milestone</url> + </repository> </repositories> </project>