GH-10083: Implement Nullability in ZK module

Related to: https://github.com/spring-projects/spring-integration/issues/10083

* Add `@org.jspecify.annotations.NullMarked` to all the packages in the ZK module
* Fix all the respective Nullability failures in the ZK module

* Fix message in assert for `candidate` in the `LeaderInitiatorFactoryBean.afterPropertiesSet()`
* Fix warning for `serialVersionUID` in the `ZookeeperLockRegistry`
This commit is contained in:
Artem Bilan
2025-06-13 12:12:57 -04:00
committed by Glenn Renfro
parent 07778e1dad
commit 2c3c0d375b
10 changed files with 88 additions and 62 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2024 the original author or authors.
* Copyright 2015-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.
@@ -115,9 +115,7 @@ public class CuratorFrameworkFactoryBean implements FactoryBean<CuratorFramework
this.lifecycleLock.lock();
try {
if (!this.running) {
if (this.client != null) {
this.client.start();
}
this.client.start();
this.running = true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2023 the original author or authors.
* Copyright 2015-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.
@@ -19,6 +19,7 @@ package org.springframework.integration.zookeeper.config;
import java.util.UUID;
import org.apache.curator.framework.CuratorFramework;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
@@ -31,6 +32,7 @@ import org.springframework.integration.leader.event.DefaultLeaderEventPublisher;
import org.springframework.integration.leader.event.LeaderEventPublisher;
import org.springframework.integration.zookeeper.leader.LeaderInitiator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Creates a {@link LeaderInitiator}.
@@ -44,21 +46,22 @@ import org.springframework.util.Assert;
public class LeaderInitiatorFactoryBean
implements FactoryBean<LeaderInitiator>, SmartLifecycle, InitializingBean, ApplicationEventPublisherAware {
private CuratorFramework client;
private @Nullable CuratorFramework client;
private Candidate candidate;
private @Nullable Candidate candidate;
private String path;
private @Nullable String path;
private LeaderInitiator leaderInitiator;
private @Nullable LeaderInitiator leaderInitiator;
private boolean autoStartup = true;
private int phase = Integer.MAX_VALUE - 1000; // NOSONAR magic number
@SuppressWarnings("NullAway.Init")
private ApplicationEventPublisher applicationEventPublisher;
private LeaderEventPublisher leaderEventPublisher;
private @Nullable LeaderEventPublisher leaderEventPublisher;
public LeaderInitiatorFactoryBean() {
}
@@ -164,21 +167,24 @@ public class LeaderInitiatorFactoryBean
@Override
public void afterPropertiesSet() {
if (this.leaderInitiator == null) {
this.leaderInitiator = new LeaderInitiator(this.client, this.candidate, this.path);
Assert.notNull(this.client, "The 'CuratorFramework' must be provided.");
Assert.notNull(this.candidate, "The 'candidate' must be provided.");
this.leaderInitiator =
StringUtils.hasText(this.path)
? new LeaderInitiator(this.client, this.candidate, this.path)
: new LeaderInitiator(this.client, this.candidate);
this.leaderInitiator.setPhase(this.phase);
this.leaderInitiator.setAutoStartup(this.autoStartup);
if (this.leaderEventPublisher != null) {
this.leaderInitiator.setLeaderEventPublisher(this.leaderEventPublisher);
}
else if (this.applicationEventPublisher != null) {
this.leaderInitiator.setLeaderEventPublisher(
new DefaultLeaderEventPublisher(this.applicationEventPublisher));
LeaderEventPublisher leaderEventPublisherToSet = this.leaderEventPublisher;
if (leaderEventPublisherToSet == null) {
leaderEventPublisherToSet = new DefaultLeaderEventPublisher(this.applicationEventPublisher);
}
this.leaderInitiator.setLeaderEventPublisher(leaderEventPublisherToSet);
}
}
@Override
public LeaderInitiator getObject() {
public @Nullable LeaderInitiator getObject() {
return this.leaderInitiator;
}

View File

@@ -1,4 +1,5 @@
/**
* Provides classes related to configuration.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.integration.zookeeper.config;

View File

@@ -1,4 +1,5 @@
/**
* Base package for zookeeper configuration.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.integration.zookeeper.config.xml;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-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.
@@ -28,12 +28,12 @@ import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.framework.recipes.leader.Participant;
import org.jspecify.annotations.Nullable;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.leader.Candidate;
import org.springframework.integration.leader.Context;
import org.springframework.integration.leader.event.LeaderEventPublisher;
import org.springframework.util.StringUtils;
/**
* Bootstrap leadership {@link Candidate candidates}
@@ -79,7 +79,7 @@ public class LeaderInitiator implements SmartLifecycle {
/**
* Leader event publisher if set
*/
private LeaderEventPublisher leaderEventPublisher;
private @Nullable LeaderEventPublisher leaderEventPublisher;
/**
* @see SmartLifecycle
@@ -94,7 +94,7 @@ public class LeaderInitiator implements SmartLifecycle {
/**
* Curator utility for selecting leaders.
*/
private volatile LeaderSelector leaderSelector;
private volatile @Nullable LeaderSelector leaderSelector;
/**
* Flag that indicates whether the leadership election for
@@ -167,15 +167,17 @@ public class LeaderInitiator implements SmartLifecycle {
if (!this.running) {
if (this.client.getState() != CuratorFrameworkState.STARTED) {
// we want to do curator start here because it needs to
// be started before leader selector and it gets a little
// be started before leader selector, and it gets a little
// complicated to control ordering via beans so that
// curator is fully started.
this.client.start();
}
this.leaderSelector = new LeaderSelector(this.client, buildLeaderPath(), new LeaderListener());
this.leaderSelector.setId(this.candidate.getId());
this.leaderSelector.autoRequeue();
this.leaderSelector.start();
LeaderSelector leaderSelectorToSet =
new LeaderSelector(this.client, buildLeaderPath(), new LeaderListener());
leaderSelectorToSet.setId(this.candidate.getId());
leaderSelectorToSet.autoRequeue();
leaderSelectorToSet.start();
this.leaderSelector = leaderSelectorToSet;
this.running = true;
LOGGER.debug("Started LeaderInitiator");
@@ -195,7 +197,10 @@ public class LeaderInitiator implements SmartLifecycle {
this.lifecycleMonitor.lock();
try {
if (this.running) {
this.leaderSelector.close();
LeaderSelector leaderSelectorToClose = this.leaderSelector;
if (leaderSelectorToClose != null) {
leaderSelectorToClose.close();
}
this.running = false;
LOGGER.debug("Stopped LeaderInitiator");
}
@@ -229,7 +234,7 @@ public class LeaderInitiator implements SmartLifecycle {
* @return the ZooKeeper path used for leadership election by Curator
*/
private String buildLeaderPath() {
String ns = StringUtils.hasText(this.namespace) ? this.namespace : DEFAULT_NAMESPACE;
String ns = this.namespace;
if (ns.charAt(0) != '/') {
ns = '/' + ns;
}
@@ -294,12 +299,16 @@ public class LeaderInitiator implements SmartLifecycle {
@Override
public boolean isLeader() {
return LeaderInitiator.this.leaderSelector.hasLeadership();
LeaderSelector leaderSelectorToCheck = LeaderInitiator.this.leaderSelector;
return leaderSelectorToCheck != null && leaderSelectorToCheck.hasLeadership();
}
@Override
public void yield() {
LeaderInitiator.this.leaderSelector.interruptLeadership();
LeaderSelector leaderSelectorToInterrupt = LeaderInitiator.this.leaderSelector;
if (leaderSelectorToInterrupt != null) {
leaderSelectorToInterrupt.interruptLeadership();
}
}
@Override
@@ -312,13 +321,17 @@ public class LeaderInitiator implements SmartLifecycle {
* @return the leader.
* @since 6.0.3
*/
public Participant getLeader() {
try {
return LeaderInitiator.this.leaderSelector.getLeader();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
public @Nullable Participant getLeader() {
LeaderSelector leaderSelectorToUse = LeaderInitiator.this.leaderSelector;
if (leaderSelectorToUse != null) {
try {
return leaderSelectorToUse.getLeader();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
return null;
}
/**
@@ -327,12 +340,16 @@ public class LeaderInitiator implements SmartLifecycle {
* @since 6.0.3
*/
public Collection<Participant> getParticipants() {
try {
return LeaderInitiator.this.leaderSelector.getParticipants();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
LeaderSelector leaderSelectorToUse = LeaderInitiator.this.leaderSelector;
if (leaderSelectorToUse != null) {
try {
return leaderSelectorToUse.getParticipants();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
return List.of();
}
@Override
@@ -352,12 +369,7 @@ public class LeaderInitiator implements SmartLifecycle {
}
@Override
public String getRole() {
return LeaderInitiator.this.candidate.getRole();
}
@Override
public Participant getLeader() {
public @Nullable Participant getLeader() {
return null;
}

View File

@@ -1,4 +1,5 @@
/**
* Temporary package until s-c-c-zookeeper is released.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.integration.zookeeper.leader;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2023 the original author or authors.
* Copyright 2015-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.
@@ -16,6 +16,7 @@
package org.springframework.integration.zookeeper.lock;
import java.io.Serial;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -63,7 +64,10 @@ public class ZookeeperLockRegistry implements ExpirableLockRegistry, DisposableB
private final Lock locksLock = new ReentrantLock();
private final Map<String, ZkLock> locks =
new LinkedHashMap<String, ZkLock>(16, 0.75F, true) {
new LinkedHashMap<>(16, 0.75F, true) {
@Serial
private static final long serialVersionUID = 7092378879531819061L;
@Override
protected boolean removeEldestEntry(Entry<String, ZkLock> eldest) {
@@ -334,9 +338,7 @@ public class ZookeeperLockRegistry implements ExpirableLockRegistry, DisposableB
}
}
catch (@SuppressWarnings("unused") TimeoutException e) {
if (future != null) {
future.cancel(true);
}
future.cancel(true);
return false;
}
catch (InterruptedException e) {

View File

@@ -1,4 +1,5 @@
/**
* Provides classes related to locking.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.integration.zookeeper.lock;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2024 the original author or authors.
* Copyright 2015-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.
@@ -18,6 +18,7 @@ package org.springframework.integration.zookeeper.metadata;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -31,6 +32,7 @@ import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.jspecify.annotations.Nullable;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.metadata.ListenableMetadataStore;
@@ -67,7 +69,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
private String encoding = StandardCharsets.UTF_8.name();
private CuratorCache cache;
private @Nullable CuratorCache cache;
private boolean autoStartup = true;
@@ -79,7 +81,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
public ZookeeperMetadataStore(CuratorFramework client) {
Assert.notNull(client, "Client cannot be null");
this.client = client; // NOSONAR
this.client = client;
}
/**
@@ -115,7 +117,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
}
@Override
public String putIfAbsent(String key, String value) {
public @Nullable String putIfAbsent(String key, String value) {
this.lock.lock();
try {
Assert.notNull(key, KEY_MUST_NOT_BE_NULL);
@@ -207,12 +209,13 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
}
@Override
public String get(String key) {
public @Nullable String get(String key) {
this.lock.lock();
try {
Assert.notNull(key, KEY_MUST_NOT_BE_NULL);
Assert.state(isRunning(), "ZookeeperMetadataStore has to be started before using.");
return this.cache.get(getPath(key))
return Objects.requireNonNull(this.cache)
.get(getPath(key))
.map(currentData -> {
// our version is more recent than the cache
if (this.updateMap.containsKey(key) &&
@@ -240,7 +243,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
}
@Override
public String remove(String key) {
public @Nullable String remove(String key) {
this.lock.lock();
try {
Assert.notNull(key, KEY_MUST_NOT_BE_NULL);
@@ -276,7 +279,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
}
public String getPath(String key) {
return "".equals(key) ? this.root : this.root + '/' + key;
return key.isEmpty() ? this.root : this.root + '/' + key;
}
@Override
@@ -344,7 +347,7 @@ public class ZookeeperMetadataStore implements ListenableMetadataStore, SmartLif
return path.replace(this.root + '/', "");
}
private record LocalChildData(String value, int version) {
private record LocalChildData(@Nullable String value, int version) {
}

View File

@@ -2,4 +2,5 @@
* Provides classes supporting the Zookeeper-based
* {@link org.springframework.integration.metadata.ListenableMetadataStore}
*/
@org.jspecify.annotations.NullMarked
package org.springframework.integration.zookeeper.metadata;