Skip to content

Commit 3ef06fe

Browse files
csviriclaude
andcommitted
build: add SpotBugs plugin and fix all reported findings
Add the SpotBugs Maven plugin (effort=Max, threshold=Medium) with a `check` execution bound to the `verify` phase across all modules, plus a documented `spotbugs-exclude.xml` for intentional/false-positive findings. Real bug fixes: - ExecutorServiceManager: synchronize start()/stop() so `started` and `configurationService` are accessed consistently with the already-locked lazyInitWorkflowExecutorService() (IS2_INCONSISTENT_SYNC, AT_STALE_THREAD_WRITE_OF_PRIMITIVE). - AbstractEventSourceHolderDependentResource: make `isCacheFillerEventSource` volatile (written under lock, read from reconcile threads). - ManagedInformerEventSource: make `cache` and `controllerConfiguration` volatile (assigned under lock in start(), read lock-free). - AbstractOperatorExtension: the builder's `namespaceDeleteTimeout` was configurable but never plumbed into the extension, so the namespace deletion timeout always used the default. Threaded it through the constructors of both LocallyRunOperatorExtension and ClusterDeployedOperatorExtension. - MicrometerMetrics.addMetadataTags(): guard against null `metadata`, which incrementCounter() already tolerates (latent NPE, NP_NULL_PARAM_DEREF). - Bootstrapper.addTemplatedFile(): close the Writer via try-with-resources and write as UTF-8 (OBL_UNSATISFIED_OBLIGATION, DM_DEFAULT_ENCODING). Default-encoding fixes (DM_DEFAULT_ENCODING), all pinned to UTF-8: - LocallyRunOperatorExtension CRD apply/delete byte conversions. - AccumulativeMappingWriter reader and PrintWriter. - ClassMappingProvider InputStreamReader. - sample mysql-schema Secret/Schema Base64 encode/decode. - sample tomcat-operator WebappReconciler ByteArrayOutputStream.toString(). Sample cleanups: - TomcatReconciler: avoid unbox-then-rebox in a log argument (BX_UNBOXING_IMMEDIATELY_REBOXED). - OperationsSampleOperator: make the config path overridable via the CONFIG_PATH env var instead of a hardcoded absolute path (DMI_HARDCODED_ABSOLUTE_FILENAME). Suppressed in spotbugs-exclude.xml (intentional / false positives): EI_EXPOSE_REP(2) and CT_CONSTRUCTOR_THROW project-wide, plus DM_EXIT in LeaderElectionManager, NP_BOOLEAN_RETURN_NULL in BooleanWithUndefined, EQ_DOESNT_OVERRIDE_EQUALS in GroupVersionKindPlural, and SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR in SSABasedGenericKubernetesResourceMatcher and ConfigLoader. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c86eeec commit 3ef06fe

17 files changed

Lines changed: 141 additions & 20 deletions

File tree

bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.File;
1919
import java.io.FileWriter;
2020
import java.io.IOException;
21+
import java.nio.charset.StandardCharsets;
2122
import java.util.Arrays;
2223
import java.util.List;
2324
import java.util.Map;
@@ -132,9 +133,10 @@ private void addTemplatedFile(
132133
targetDir == null ? projectDir : targetDir,
133134
targetFileName == null ? fileName : targetFileName);
134135
FileUtils.forceMkdir(targetFile.getParentFile());
135-
var writer = new FileWriter(targetFile);
136-
mustache.execute(writer, values);
137-
writer.flush();
136+
try (var writer = new FileWriter(targetFile, StandardCharsets.UTF_8)) {
137+
mustache.execute(writer, values);
138+
writer.flush();
139+
}
138140
} catch (IOException e) {
139141
throw new RuntimeException(e);
140142
}

micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ private void addMetadataTags(
277277
addTagOmittingOnEmptyValue(NAMESPACE, resourceID.getNamespace().orElse(null), tags, prefixed);
278278
}
279279
addTag(SCOPE, getScope(resourceID), tags, prefixed);
280-
final var gvk = (GroupVersionKind) metadata.get(Constants.RESOURCE_GVK_KEY);
280+
final var gvk =
281+
metadata == null ? null : (GroupVersionKind) metadata.get(Constants.RESOURCE_GVK_KEY);
281282
if (gvk != null) {
282283
addGVKTags(gvk, tags, prefixed);
283284
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public ScheduledExecutorService scheduledExecutorService() {
132132
return scheduledExecutorService;
133133
}
134134

135-
public void start(ConfigurationService configurationService) {
135+
public synchronized void start(ConfigurationService configurationService) {
136136
if (!started) {
137137
this.configurationService = configurationService; // used to lazy init workflow executor
138138
this.cachingExecutorService = Executors.newCachedThreadPool();
@@ -142,7 +142,7 @@ public void start(ConfigurationService configurationService) {
142142
}
143143
}
144144

145-
public void stop(Duration gracefulShutdownTimeout) {
145+
public synchronized void stop(Duration gracefulShutdownTimeout) {
146146
try {
147147
log.debug("Closing executor");
148148
var parallelExec = Executors.newFixedThreadPool(3);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public abstract class AbstractEventSourceHolderDependentResource<
3636

3737
private T eventSource;
3838
private final Class<R> resourceType;
39-
private boolean isCacheFillerEventSource;
39+
private volatile boolean isCacheFillerEventSource;
4040
protected String eventSourceNameToUse;
4141

4242
@SuppressWarnings("unchecked")

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ public abstract class ManagedInformerEventSource<
6161
Configurable<C> {
6262

6363
private static final Logger log = LoggerFactory.getLogger(ManagedInformerEventSource.class);
64-
private InformerManager<R, C> cache;
64+
private volatile InformerManager<R, C> cache;
6565
private final boolean comparableResourceVersions;
66-
private ControllerConfiguration<R> controllerConfiguration;
66+
private volatile ControllerConfiguration<R> controllerConfiguration;
6767
private final C configuration;
6868
private final Map<String, Function<R, List<String>>> indexers = new HashMap<>();
6969
protected TemporaryResourceCache<R> temporaryResourceCache;

operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public abstract class AbstractOperatorExtension
6161
protected final boolean preserveNamespaceOnError;
6262
protected final boolean skipNamespaceDeletion;
6363
protected final boolean waitForNamespaceDeletion;
64-
protected final int namespaceDeleteTimeout = DEFAULT_NAMESPACE_DELETE_TIMEOUT;
64+
protected final int namespaceDeleteTimeout;
6565
protected final Function<ExtensionContext, String> namespaceNameSupplier;
6666
protected final Function<ExtensionContext, String> perClassNamespaceNameSupplier;
6767

@@ -74,6 +74,7 @@ protected AbstractOperatorExtension(
7474
boolean preserveNamespaceOnError,
7575
boolean skipNamespaceDeletion,
7676
boolean waitForNamespaceDeletion,
77+
int namespaceDeleteTimeout,
7778
KubernetesClient kubernetesClient,
7879
KubernetesClient infrastructureKubernetesClient,
7980
Function<ExtensionContext, String> namespaceNameSupplier,
@@ -90,6 +91,7 @@ protected AbstractOperatorExtension(
9091
this.preserveNamespaceOnError = preserveNamespaceOnError;
9192
this.skipNamespaceDeletion = skipNamespaceDeletion;
9293
this.waitForNamespaceDeletion = waitForNamespaceDeletion;
94+
this.namespaceDeleteTimeout = namespaceDeleteTimeout;
9395
this.namespaceNameSupplier = namespaceNameSupplier;
9496
this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
9597
}

operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private ClusterDeployedOperatorExtension(
5353
boolean preserveNamespaceOnError,
5454
boolean skipNamespaceDeletion,
5555
boolean waitForNamespaceDeletion,
56+
int namespaceDeleteTimeout,
5657
boolean oneNamespacePerClass,
5758
KubernetesClient kubernetesClient,
5859
KubernetesClient infrastructureKubernetesClient,
@@ -65,6 +66,7 @@ private ClusterDeployedOperatorExtension(
6566
preserveNamespaceOnError,
6667
skipNamespaceDeletion,
6768
waitForNamespaceDeletion,
69+
namespaceDeleteTimeout,
6870
kubernetesClient,
6971
infrastructureKubernetesClient,
7072
namespaceNameSupplier,
@@ -231,6 +233,7 @@ public ClusterDeployedOperatorExtension build() {
231233
preserveNamespaceOnError,
232234
skipNamespaceDeletion,
233235
waitForNamespaceDeletion,
236+
namespaceDeleteTimeout,
234237
oneNamespacePerClass,
235238
kubernetesClient,
236239
infrastructureKubernetesClient,

operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private LocallyRunOperatorExtension(
8787
boolean preserveNamespaceOnError,
8888
boolean skipNamespaceDeletion,
8989
boolean waitForNamespaceDeletion,
90+
int namespaceDeleteTimeout,
9091
boolean oneNamespacePerClass,
9192
KubernetesClient kubernetesClient,
9293
KubernetesClient infrastructureKubernetesClient,
@@ -103,6 +104,7 @@ private LocallyRunOperatorExtension(
103104
preserveNamespaceOnError,
104105
skipNamespaceDeletion,
105106
waitForNamespaceDeletion,
107+
namespaceDeleteTimeout,
106108
kubernetesClient,
107109
infrastructureKubernetesClient,
108110
namespaceNameSupplier,
@@ -184,7 +186,8 @@ public static void applyCrd(String resourceTypeName, KubernetesClient client) {
184186
private static void applyCrd(String crdString, String path, KubernetesClient client) {
185187
try {
186188
LOGGER.debug("Applying CRD: {}", crdString);
187-
final var crd = client.load(new ByteArrayInputStream(crdString.getBytes()));
189+
final var crd =
190+
client.load(new ByteArrayInputStream(crdString.getBytes(StandardCharsets.UTF_8)));
188191
crd.serverSideApply();
189192
appliedCRDs.add(new AppliedCRD.FileCRD(crdString, path));
190193
Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little
@@ -446,7 +449,10 @@ record FileCRD(String crdString, String path) implements AppliedCRD {
446449
public void delete(KubernetesClient client) {
447450
try {
448451
LOGGER.debug("Deleting CRD: {}", crdString);
449-
final var items = client.load(new ByteArrayInputStream(crdString.getBytes())).items();
452+
final var items =
453+
client
454+
.load(new ByteArrayInputStream(crdString.getBytes(StandardCharsets.UTF_8)))
455+
.items();
450456
if (items == null || items.isEmpty() || items.get(0) == null) {
451457
LOGGER.warn("Could not determine CRD name from yaml: {}", path);
452458
return;
@@ -620,6 +626,7 @@ public LocallyRunOperatorExtension build() {
620626
preserveNamespaceOnError,
621627
skipNamespaceDeletion,
622628
waitForNamespaceDeletion,
629+
namespaceDeleteTimeout,
623630
oneNamespacePerClass,
624631
kubernetesClient,
625632
infrastructureKubernetesClient,

operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AccumulativeMappingWriter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.InputStreamReader;
2121
import java.io.PrintWriter;
22+
import java.nio.charset.StandardCharsets;
2223
import java.util.Map;
2324
import java.util.concurrent.ConcurrentHashMap;
2425
import java.util.stream.Collectors;
@@ -51,7 +52,8 @@ public AccumulativeMappingWriter loadExistingMappings() {
5152
.getResource(StandardLocation.CLASS_OUTPUT, "", resourcePath);
5253

5354
try (BufferedReader bufferedReader =
54-
new BufferedReader(new InputStreamReader(readonlyResource.openInputStream()))) {
55+
new BufferedReader(
56+
new InputStreamReader(readonlyResource.openInputStream(), StandardCharsets.UTF_8))) {
5557
final var existingLines =
5658
bufferedReader
5759
.lines()
@@ -81,7 +83,7 @@ public void flush() {
8183
processingEnvironment
8284
.getFiler()
8385
.createResource(StandardLocation.CLASS_OUTPUT, "", resourcePath);
84-
printWriter = new PrintWriter(resource.openOutputStream());
86+
printWriter = new PrintWriter(resource.openOutputStream(), false, StandardCharsets.UTF_8);
8587

8688
for (Map.Entry<String, String> entry : mappings.entrySet()) {
8789
printWriter.println(entry.getKey() + "," + entry.getValue());

operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/ClassMappingProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.InputStreamReader;
2121
import java.net.URL;
22+
import java.nio.charset.StandardCharsets;
2223
import java.util.Enumeration;
2324
import java.util.HashMap;
2425
import java.util.Iterator;
@@ -69,7 +70,8 @@ static <T, V> Map<T, V> provide(final String resourcePath, T key, V value) {
6970
}
7071

7172
private static List<String> retrieveClassNamePairs(URL url) throws IOException {
72-
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
73+
try (BufferedReader br =
74+
new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
7375
return br.lines().collect(Collectors.toList());
7476
}
7577
}

0 commit comments

Comments
 (0)