diff --git a/.mvn/jvm.config b/.mvn/jvm.config index 23acc6ad8..52c05b116 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1 +1,11 @@ -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED diff --git a/.mvn/maven.config b/.mvn/maven.config index 4b9372844..09af02f19 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,2 +1,3 @@ -DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local +-Djspecify.enabled=true -P spring diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/RangeConverter.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/RangeConverter.java index 41d90e33b..8c2701f1e 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/RangeConverter.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/RangeConverter.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.autoconfigure; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.file.transform.Range; import org.springframework.core.convert.converter.Converter; @@ -31,7 +33,7 @@ public class RangeConverter implements Converter { @Override - public Range convert(String source) { + public @Nullable Range convert(String source) { if (source == null) { return null; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobAutoConfiguration.java index 5531aa574..04f0dac01 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobAutoConfiguration.java @@ -22,8 +22,7 @@ import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.Step; -import org.springframework.batch.core.step.builder.SimpleStepBuilder; -import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; import org.springframework.batch.infrastructure.item.ItemProcessor; import org.springframework.batch.infrastructure.item.ItemReader; import org.springframework.batch.infrastructure.item.ItemWriter; @@ -78,16 +77,16 @@ private void validateProperties(SingleStepJobProperties properties) { @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.batch.job", name = "job-name") public Job job(ItemReader> itemReader, ItemWriter> itemWriter) { - - SimpleStepBuilder, Map> stepBuilder = new StepBuilder( - this.properties.getStepName(), this.jobRepository) - ., Map>chunk(this.properties.getChunkSize(), this.transactionManager) + Assert.state(properties.getStepName() != null, "A step name is required"); + Assert.state(properties.getChunkSize() != null, "A chunkSize is required"); + var chunkOrientedStepBuilder = new ChunkOrientedStepBuilder(properties.getStepName(), this.jobRepository, + this.properties.getChunkSize()) + .transactionManager(this.transactionManager) .reader(itemReader); + chunkOrientedStepBuilder.processor(this.itemProcessor); + Step step = chunkOrientedStepBuilder.writer(itemWriter).build(); - stepBuilder.processor(this.itemProcessor); - - Step step = stepBuilder.writer(itemWriter).build(); - + Assert.state(this.properties.getJobName() != null, "A job name is required"); return new JobBuilder(this.properties.getJobName(), this.jobRepository).start(step).build(); } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobProperties.java index b8be64bde..b0a49cc4e 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/SingleStepJobProperties.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.autoconfigure; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -30,23 +32,23 @@ public class SingleStepJobProperties { /** * Name of the step in the single step job. */ - private String stepName; + private @Nullable String stepName; /** * The number of items to process per transaction or chunk. */ - private Integer chunkSize; + private @Nullable Integer chunkSize; /** * The name of the job. */ - private String jobName; + private @Nullable String jobName; /** * Name of the step in the single step job. * @return name */ - public String getStepName() { + public @Nullable String getStepName() { return stepName; } @@ -54,7 +56,7 @@ public String getStepName() { * Set the name of the step. * @param stepName name */ - public void setStepName(String stepName) { + public void setStepName(@Nullable String stepName) { this.stepName = stepName; } @@ -62,7 +64,7 @@ public void setStepName(String stepName) { * The number of items to process per transaction/chunk. * @return number of items */ - public Integer getChunkSize() { + public @Nullable Integer getChunkSize() { return chunkSize; } @@ -70,7 +72,7 @@ public Integer getChunkSize() { * Set the number of items within a transaction/chunk. * @param chunkSize number of items */ - public void setChunkSize(Integer chunkSize) { + public void setChunkSize(@Nullable Integer chunkSize) { this.chunkSize = chunkSize; } @@ -78,7 +80,7 @@ public void setChunkSize(Integer chunkSize) { * The name of the job. * @return name */ - public String getJobName() { + public @Nullable String getJobName() { return jobName; } @@ -86,7 +88,7 @@ public String getJobName() { * Set the name of the job. * @param jobName name */ - public void setJobName(String jobName) { + public void setJobName(@Nullable String jobName) { this.jobName = jobName; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderAutoConfiguration.java index 0f2516ef7..7615e0db1 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderAutoConfiguration.java @@ -42,6 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.task.batch.autoconfigure.RangeConverter; import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; /** * Autconfiguration for a {@code FlatFileItemReader}. @@ -71,6 +72,8 @@ public FlatFileItemReader> itemReader(@Autowired(required = @Autowired(required = false) LineMapper> lineMapper, @Autowired(required = false) LineCallbackHandler skippedLinesCallback, @Autowired(required = false) RecordSeparatorPolicy recordSeparatorPolicy) { + Assert.state(this.properties.getName() != null, "A name is required"); + Assert.state(this.properties.getResource() != null, "A resource is required"); FlatFileItemReaderBuilder> mapFlatFileItemReaderBuilder = new FlatFileItemReaderBuilder>() .name(this.properties.getName()) .resource(this.properties.getResource()) @@ -90,6 +93,7 @@ public FlatFileItemReader> itemReader(@Autowired(required = mapFlatFileItemReaderBuilder.skippedLinesCallback(skippedLinesCallback); if (this.properties.isDelimited()) { + Assert.state(this.properties.getNames() != null, "Names are required"); mapFlatFileItemReaderBuilder.delimited() .quoteCharacter(this.properties.getQuoteCharacter()) .delimiter(this.properties.getDelimiter()) @@ -99,9 +103,14 @@ public FlatFileItemReader> itemReader(@Autowired(required = .fieldSetMapper(new MapFieldSetMapper()); } else if (this.properties.isFixedLength()) { + Assert.state(this.properties.getNames() != null, "Names are required"); RangeConverter rangeConverter = new RangeConverter(); List ranges = new ArrayList<>(); - this.properties.getRanges().forEach(range -> ranges.add(rangeConverter.convert(range))); + this.properties.getRanges().forEach(range -> { + Range result = rangeConverter.convert(range); + Assert.state(result != null, "Range String could not converted to non-null range"); + ranges.add(result); + }); mapFlatFileItemReaderBuilder.fixedLength() .columns(ranges.toArray(new Range[0])) .names(this.properties.getNames()) diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderProperties.java index 14d4ab8ea..e72b0f8aa 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemReaderProperties.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.file.FlatFileItemReader; import org.springframework.batch.infrastructure.item.file.transform.DelimitedLineTokenizer; import org.springframework.batch.infrastructure.item.file.transform.Range; @@ -44,7 +46,7 @@ public class FlatFileItemReaderProperties { * {@link org.springframework.batch.infrastructure.item.ExecutionContext}. Required if * {@link #setSaveState} is set to {@code true}. */ - private String name; + private @Nullable String name; /** * Configure the maximum number of items to be read. @@ -64,7 +66,7 @@ public class FlatFileItemReaderProperties { /** * The {@link Resource} to be used as input. */ - private Resource resource; + private @Nullable Resource resource; /** * Configure whether the reader should be in strict mode (require the input @@ -118,7 +120,7 @@ public class FlatFileItemReaderProperties { /** * The names of the fields to be parsed from the file. */ - private String[] names; + private String @Nullable [] names; /** * Indicates whether the number of tokens must match the number of configured fields. @@ -150,7 +152,7 @@ public void setSaveState(boolean saveState) { * keys. * @return the name */ - public String getName() { + public @Nullable String getName() { return this.name; } @@ -161,7 +163,7 @@ public String getName() { * @param name name of the reader instance * @see org.springframework.batch.infrastructure.item.ItemStreamSupport#setName(String) */ - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } @@ -219,7 +221,7 @@ public void setComments(List comments) { * The input file for the {@code FlatFileItemReader}. * @return a Resource */ - public Resource getResource() { + public @Nullable Resource getResource() { return this.resource; } @@ -228,7 +230,7 @@ public Resource getResource() { * @param resource the input to the reader. * @see FlatFileItemReader#setResource(Resource) */ - public void setResource(Resource resource) { + public void setResource(@Nullable Resource resource) { this.resource = resource; } @@ -391,7 +393,7 @@ public void setRanges(List ranges) { * Names of each column. * @return names */ - public String[] getNames() { + public String @Nullable [] getNames() { return this.names; } @@ -399,7 +401,7 @@ public String[] getNames() { * The names of the fields to be parsed from the file. * @param names names of fields */ - public void setNames(String[] names) { + public void setNames(String @Nullable [] names) { this.names = names; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterAutoConfiguration.java index 3ffb705da..2d85b1f20 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterAutoConfiguration.java @@ -35,6 +35,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.io.WritableResource; +import org.springframework.util.Assert; /** * Autoconfiguration for a {@code FlatFileItemWriter}. @@ -47,7 +48,7 @@ @AutoConfigureAfter(BatchAutoConfiguration.class) public class FlatFileItemWriterAutoConfiguration { - private FlatFileItemWriterProperties properties; + private final FlatFileItemWriterProperties properties; @Autowired(required = false) private LineAggregator> lineAggregator; @@ -78,7 +79,8 @@ else if ((this.properties.isFormatted() || this.properties.isDelimited()) && thi throw new IllegalStateException( "A LineAggregator must be configured if the " + "output is not formatted or delimited"); } - + Assert.state(this.properties.getName() != null, "name must not be null"); + Assert.state(this.properties.getResource() != null, "resource must not be null"); FlatFileItemWriterBuilder> builder = new FlatFileItemWriterBuilder>() .name(this.properties.getName()) .resource((WritableResource) this.properties.getResource()) @@ -101,10 +103,13 @@ else if ((this.properties.isFormatted() || this.properties.isDelimited()) && thi delimitedBuilder.fieldExtractor(this.fieldExtractor); } else { + Assert.state(this.properties.getNames() != null, "names must not be null"); delimitedBuilder.fieldExtractor(new MapFieldExtractor(this.properties.getNames())); } } else if (this.properties.isFormatted()) { + Assert.state(this.properties.getFormat() != null, "format must not be null"); + FlatFileItemWriterBuilder.FormattedBuilder> formattedBuilder = builder.formatted() .format(this.properties.getFormat()) .locale(this.properties.getLocale()) @@ -115,6 +120,7 @@ else if (this.properties.isFormatted()) { formattedBuilder.fieldExtractor(this.fieldExtractor); } else { + Assert.state(this.properties.getNames() != null, "names must not be null"); formattedBuilder.fieldExtractor(new MapFieldExtractor(this.properties.getNames())); } } @@ -131,7 +137,7 @@ else if (this.lineAggregator != null) { */ public static class MapFieldExtractor implements FieldExtractor> { - private String[] names; + private final String[] names; public MapFieldExtractor(String[] names) { this.names = names; diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterProperties.java index 0f7c4f9ef..e1ce95a92 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/FlatFileItemWriterProperties.java @@ -18,6 +18,8 @@ import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.file.FlatFileItemWriter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.io.Resource; @@ -34,7 +36,7 @@ public class FlatFileItemWriterProperties { /** * The {@link Resource} to be used as output. */ - private Resource resource; + private @Nullable Resource resource; /** * Configure the use of the {@code DelimitedLineAggregator} to generate the output per @@ -51,7 +53,7 @@ public class FlatFileItemWriterProperties { /** * Configure the format the {@code FormatterLineAggregator} uses for each item. */ - private String format; + private @Nullable String format; /** * Configure the {@code Locale} to use when generating the output. @@ -88,7 +90,7 @@ public class FlatFileItemWriterProperties { /** * Names of the fields to be extracted into the output. */ - private String[] names; + private String @Nullable [] names; /** * Configure if the output file is found if it should be appended to. Defaults to @@ -107,7 +109,7 @@ public class FlatFileItemWriterProperties { * {@link org.springframework.batch.infrastructure.item.ExecutionContext}. Required if * {@link #setSaveState} is set to {@code true}. */ - private String name; + private @Nullable String name; /** * Returns the configured value of whether the state of the reader is persisted. @@ -157,7 +159,7 @@ public void setSaveState(boolean saveState) { * keys. * @return the name */ - public String getName() { + public @Nullable String getName() { return this.name; } @@ -168,7 +170,7 @@ public String getName() { * @param name name of the reader instance * @see org.springframework.batch.infrastructure.item.ItemStreamSupport#setName(String) */ - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } @@ -176,7 +178,7 @@ public void setName(String name) { * The output file for the {@code FlatFileItemWriter}. * @return a {@code Resource} */ - public Resource getResource() { + public @Nullable Resource getResource() { return this.resource; } @@ -185,7 +187,7 @@ public Resource getResource() { * @param resource the input to the reader. * @see FlatFileItemWriter#setResource */ - public void setResource(Resource resource) { + public void setResource(@Nullable Resource resource) { this.resource = resource; } @@ -227,7 +229,7 @@ public void setDelimiter(String delimiter) { * Names of the fields to be extracted into the output. * @return An array of field names */ - public String[] getNames() { + public String @Nullable [] getNames() { return names; } @@ -235,7 +237,7 @@ public String[] getNames() { * Provide an ordered array of field names used to generate the output of a file. * @param names An array of field names */ - public void setNames(String[] names) { + public void setNames(String @Nullable [] names) { this.names = names; } @@ -385,7 +387,7 @@ public void setTransactional(boolean transactional) { * Format used with the {@code FormatterLineAggregator}. * @return the format for each item's output. */ - public String getFormat() { + public @Nullable String getFormat() { return format; } @@ -393,7 +395,7 @@ public String getFormat() { * Configure the format the {@code FormatterLineAggregator} will use for each item. * @param format the format for each item's output. */ - public void setFormat(String format) { + public void setFormat(@Nullable String format) { this.format = format; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/package-info.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/package-info.java index acab384f1..c0b81c971 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/package-info.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/flatfile/package-info.java @@ -18,4 +18,7 @@ * Auto-configuration classes for flat file item readers and writers in single-step batch * jobs. */ +@NullMarked package org.springframework.cloud.task.batch.autoconfigure.flatfile; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterAutoConfiguration.java index 02a2ae59e..9eb8d5c87 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.util.Assert; /** * Autconfiguration for a {@code JdbcBatchItemWriter}. @@ -85,7 +86,7 @@ public JdbcBatchItemWriter> itemWriter() { catch (Exception ex) { logger.info("Using Default Data Source for the JdbcBatchItemWriter"); } - + Assert.state(this.properties.getSql() != null, "sql must not be null"); JdbcBatchItemWriterBuilder> jdbcBatchItemWriterBuilder = new JdbcBatchItemWriterBuilder>() .dataSource(writerDataSource) .sql(this.properties.getSql()); diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterProperties.java index b9ffdf774..86b0b7bda 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcBatchItemWriterProperties.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.autoconfigure.jdbc; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -31,12 +33,12 @@ public class JdbcBatchItemWriterProperties { * The name used to calculate the key within the * {@link org.springframework.batch.infrastructure.item.ExecutionContext}. */ - private String name; + private @Nullable String name; /** * The SQL statement to be used to update the database. */ - private String sql; + private @Nullable String sql; /** * If set to {@code true}, confirms that every insert results in the update of at @@ -47,7 +49,7 @@ public class JdbcBatchItemWriterProperties { /** * @return The current sql statement used to update the database. */ - public String getSql() { + public @Nullable String getSql() { return sql; } @@ -55,7 +57,7 @@ public String getSql() { * Sets the sql statement to be used to update the database. * @param sql the sql statement to be used. */ - public void setSql(String sql) { + public void setSql(@Nullable String sql) { this.sql = sql; } @@ -80,7 +82,7 @@ public void setAssertUpdates(boolean assertUpdates) { * keys. * @return the name */ - public String getName() { + public @Nullable String getName() { return name; } @@ -90,7 +92,7 @@ public String getName() { * @param name name of the writer instance * @see org.springframework.batch.infrastructure.item.ItemStreamSupport#setName(String) */ - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderAutoConfiguration.java index 64b8b3871..d1e74a88a 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderAutoConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.Assert; /** * @author Michael Minella @@ -84,6 +85,8 @@ public JdbcCursorItemReader> itemReader( logger.info("Using Default Data Source for the JdbcCursorItemReader"); } + Assert.state(this.properties.getSql() != null, "sql must not be null"); + Assert.state(this.properties.getName() != null, "name must not be null"); return new JdbcCursorItemReaderBuilder>().name(this.properties.getName()) .currentItemCount(this.properties.getCurrentItemCount()) .dataSource(readerDataSource) diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderProperties.java index f374c1361..d695b5132 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/JdbcCursorItemReaderProperties.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.autoconfigure.jdbc; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -38,7 +40,7 @@ public class JdbcCursorItemReaderProperties { * Returns the configured value of the name used to calculate {@code ExecutionContext} * keys. */ - private String name; + private @Nullable String name; /** * Configure the maximum number of items to be read. @@ -93,7 +95,7 @@ public class JdbcCursorItemReaderProperties { /** * The SQL query to be executed. */ - private String sql; + private @Nullable String sql; /** * Returns the configured value of if the state of the reader will be persisted. @@ -120,7 +122,7 @@ public void setSaveState(boolean saveState) { * keys. * @return the name */ - public String getName() { + public @Nullable String getName() { return this.name; } @@ -131,7 +133,7 @@ public String getName() { * @param name name of the reader instance * @see org.springframework.batch.infrastructure.item.ItemStreamSupport#setName(String) */ - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } @@ -297,7 +299,7 @@ public void setUseSharedExtendedConnection(boolean useSharedExtendedConnection) * Returns the SQL query to be executed. * @return the SQL query */ - public String getSql() { + public @Nullable String getSql() { return sql; } @@ -306,7 +308,7 @@ public String getSql() { * @param sql the query * @see org.springframework.batch.infrastructure.item.database.builder.JdbcCursorItemReaderBuilder#sql(String) */ - public void setSql(String sql) { + public void setSql(@Nullable String sql) { this.sql = sql; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/package-info.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/package-info.java index cfad96e8a..129832cfe 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/package-info.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/jdbc/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration classes for JDBC item readers and writers in single-step batch jobs. */ +@NullMarked package org.springframework.cloud.task.batch.autoconfigure.jdbc; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderAutoConfiguration.java index 63aad9b48..420446dee 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderAutoConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.kafka.autoconfigure.KafkaProperties; import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -63,6 +64,8 @@ public KafkaItemReader> kafkaItemReader( kafkaItemReaderProperties.setPartitions(new ArrayList<>(1)); kafkaItemReaderProperties.getPartitions().add(0); } + Assert.state(kafkaItemReaderProperties.getName() != null, "name must not be null"); + Assert.state(kafkaItemReaderProperties.getTopic() != null, "topic must not be null"); return new KafkaItemReaderBuilder>() .partitions(kafkaItemReaderProperties.getPartitions()) .consumerProperties(consumerProperties) diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderProperties.java index d454318ce..a37c02a28 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemReaderProperties.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -34,12 +36,12 @@ public class KafkaItemReaderProperties { * The name used to calculate the key within the * {@link org.springframework.batch.infrastructure.item.ExecutionContext}. */ - private String name; + private @Nullable String name; /** * The topic name from which the messages is read. */ - private String topic; + private @Nullable String topic; /** * A list of partitions to manually assign to the consumer. Defaults to a single entry @@ -67,7 +69,7 @@ public class KafkaItemReaderProperties { * keys. * @return the name */ - public String getName() { + public @Nullable String getName() { return name; } @@ -77,7 +79,7 @@ public String getName() { * @param name name of the writer instance * @see org.springframework.batch.infrastructure.item.ItemStreamSupport#setName(String) */ - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } @@ -85,7 +87,7 @@ public void setName(String name) { * Returns the name of the topic from which messages will be read. * @return the name of the topic. */ - public String getTopic() { + public @Nullable String getTopic() { return topic; } @@ -93,7 +95,7 @@ public String getTopic() { * The topic name from which the messages will be read. * @param topic name of the topic */ - public void setTopic(String topic) { + public void setTopic(@Nullable String topic) { this.topic = topic; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterAutoConfiguration.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterAutoConfiguration.java index 3902f67ce..cb5e525a1 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterAutoConfiguration.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterAutoConfiguration.java @@ -57,6 +57,7 @@ public class KafkaItemWriterAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.batch.job.kafkaitemwriter", name = "topic") + @SuppressWarnings("NullAway") public KafkaItemWriter> kafkaItemWriter( KafkaItemWriterProperties kafkaItemWriterProperties, ProducerFactory> producerFactory, diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterProperties.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterProperties.java index fbc56af49..f7eb31002 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterProperties.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/KafkaItemWriterProperties.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.autoconfigure.kafka; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -30,7 +32,7 @@ public class KafkaItemWriterProperties { /** * The topic name from which the messages are written. */ - private String topic; + private @Nullable String topic; /** * Indicate whether the items being passed to the writer are all to be sent as delete @@ -42,7 +44,7 @@ public class KafkaItemWriterProperties { * Returns the name of the topic from which messages will be written. * @return the name of the topic. */ - public String getTopic() { + public @Nullable String getTopic() { return topic; } @@ -50,7 +52,7 @@ public String getTopic() { * The topic name from which the messages are written. * @param topic name of the topic */ - public void setTopic(String topic) { + public void setTopic(@Nullable String topic) { this.topic = topic; } diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/package-info.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/package-info.java index 5e901448f..c86581f9c 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/package-info.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/kafka/package-info.java @@ -18,4 +18,7 @@ * Auto-configuration classes for Kafka item readers and writers in single-step batch * jobs. */ +@NullMarked package org.springframework.cloud.task.batch.autoconfigure.kafka; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/package-info.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/package-info.java index a84256d25..95aab2d07 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/package-info.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration classes for Spring Cloud Task single-step batch jobs. */ +@NullMarked package org.springframework.cloud.task.batch.autoconfigure; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/rabbit/package-info.java b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/rabbit/package-info.java index ba64194db..1674ca977 100644 --- a/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/rabbit/package-info.java +++ b/spring-cloud-starter-single-step-batch-job/src/main/java/org/springframework/cloud/task/batch/autoconfigure/rabbit/package-info.java @@ -18,4 +18,7 @@ * Auto-configuration classes for RabbitMQ item readers and writers in single-step batch * jobs. */ +@NullMarked package org.springframework.cloud.task.batch.autoconfigure.rabbit; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java index 83c96db1f..6bd2c59b4 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java @@ -20,6 +20,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.FactoryBean; @@ -42,9 +44,9 @@ */ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean { - private TaskBatchExecutionListener listener; + private @Nullable TaskBatchExecutionListener listener; - private DataSource dataSource; + private @Nullable DataSource dataSource; private TaskExplorer taskExplorer; @@ -56,7 +58,7 @@ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean jobs, diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/package-info.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/package-info.java index c41ff1e43..6fdd602c8 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/package-info.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/package-info.java @@ -17,4 +17,7 @@ /** * Configuration classes for Spring Cloud Task Batch integration. */ +@NullMarked package org.springframework.cloud.task.batch.configuration; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/TaskJobLauncherApplicationRunner.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/TaskJobLauncherApplicationRunner.java index 3be76f00f..1bc894d0d 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/TaskJobLauncherApplicationRunner.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/TaskJobLauncherApplicationRunner.java @@ -28,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.job.Job; @@ -79,7 +80,7 @@ public class TaskJobLauncherApplicationRunner extends JobLauncherApplicationRunn private final List jobExecutionList = new ArrayList<>(); - private ApplicationEventPublisher taskApplicationEventPublisher; + private @Nullable ApplicationEventPublisher taskApplicationEventPublisher; private final TaskBatchProperties taskBatchProperties; @@ -183,7 +184,10 @@ private void monitorJobExecutions() { private BatchStatus getCurrentBatchStatus(JobExecution jobExecution) { if (jobExecution.getStatus().isRunning()) { - return this.taskJobRepository.getJobExecution(jobExecution.getId()).getStatus(); + JobExecution jobExecutionWithStatus = this.taskJobRepository.getJobExecution(jobExecution.getId()); + Assert.state(jobExecutionWithStatus != null, + "Job execution with id " + jobExecution.getId() + " not found"); + return jobExecutionWithStatus.getStatus(); } return jobExecution.getStatus(); } diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/package-info.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/package-info.java index 743bd94a6..2676f813b 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/package-info.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/handler/package-info.java @@ -17,4 +17,7 @@ /** * Handler components for Spring Cloud Task Batch integration. */ +@NullMarked package org.springframework.cloud.task.batch.handler; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java index 9d7c1a726..5ecce2c16 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java @@ -36,6 +36,7 @@ public class TaskBatchExecutionListener implements JobExecutionListener, Ordered private static final Log logger = LogFactory.getLog(TaskBatchExecutionListener.class); + @SuppressWarnings("NullAway.Init") private TaskExecution taskExecution; private final TaskBatchDao taskBatchDao; diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java index ff0009bf9..adeb17b22 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java @@ -17,4 +17,7 @@ /** * Listener components for Spring Cloud Task Batch integration. */ +@NullMarked package org.springframework.cloud.task.batch.listener; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java index c9584bcab..3e0f6d258 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java @@ -17,4 +17,7 @@ /** * Support classes for Spring Cloud Task Batch listener implementations. */ +@NullMarked package org.springframework.cloud.task.batch.listener.support; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java index dda852976..dcd189d15 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java @@ -21,6 +21,7 @@ import jakarta.persistence.EntityManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.batch.infrastructure.support.transaction.ResourcelessTransactionManager; import org.springframework.cloud.task.repository.TaskExplorer; @@ -36,6 +37,7 @@ import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.Assert; /** * Default implementation of the TaskConfigurer interface. If no {@link TaskConfigurer} @@ -56,17 +58,17 @@ public class DefaultTaskConfigurer implements TaskConfigurer { private static final Log logger = LogFactory.getLog(DefaultTaskConfigurer.class); - private TaskProperties taskProperties; + private @Nullable TaskProperties taskProperties; private TaskRepository taskRepository; private TaskExplorer taskExplorer; - private PlatformTransactionManager transactionManager; + private @Nullable PlatformTransactionManager transactionManager; - private DataSource dataSource; + private @Nullable DataSource dataSource; - private ApplicationContext context; + private @Nullable ApplicationContext context; public DefaultTaskConfigurer() { this(TaskProperties.DEFAULT_TABLE_PREFIX); @@ -87,7 +89,7 @@ public DefaultTaskConfigurer(TaskProperties taskProperties) { * repository. If none is provided, a Map will be used (not recommended for production * use). */ - public DefaultTaskConfigurer(DataSource dataSource) { + public DefaultTaskConfigurer(@Nullable DataSource dataSource) { this(dataSource, TaskProperties.DEFAULT_TABLE_PREFIX, null); } @@ -100,7 +102,7 @@ public DefaultTaskConfigurer(DataSource dataSource) { * @param taskProperties the task properties used to obtain tablePrefix if not set by * tablePrefix field. */ - public DefaultTaskConfigurer(DataSource dataSource, TaskProperties taskProperties) { + public DefaultTaskConfigurer(@Nullable DataSource dataSource, TaskProperties taskProperties) { this(dataSource, null, null, taskProperties); } @@ -109,7 +111,7 @@ public DefaultTaskConfigurer(DataSource dataSource, TaskProperties taskPropertie * @param tablePrefix the prefix to apply to the task table names used by task * infrastructure. */ - public DefaultTaskConfigurer(String tablePrefix) { + public DefaultTaskConfigurer(@Nullable String tablePrefix) { this(null, tablePrefix, null); } @@ -120,7 +122,7 @@ public DefaultTaskConfigurer(String tablePrefix) { * @param taskProperties the task properties used to obtain tablePrefix if not set by * tablePrefix field. */ - public DefaultTaskConfigurer(String tablePrefix, TaskProperties taskProperties) { + public DefaultTaskConfigurer(@Nullable String tablePrefix, TaskProperties taskProperties) { this(null, tablePrefix, null, taskProperties); } @@ -133,7 +135,8 @@ public DefaultTaskConfigurer(String tablePrefix, TaskProperties taskProperties) * infrastructure. * @param context the context to be used. */ - public DefaultTaskConfigurer(DataSource dataSource, String tablePrefix, ApplicationContext context) { + public DefaultTaskConfigurer(@Nullable DataSource dataSource, @Nullable String tablePrefix, + @Nullable ApplicationContext context) { this(dataSource, tablePrefix, context, null); } @@ -148,8 +151,8 @@ public DefaultTaskConfigurer(DataSource dataSource, String tablePrefix, Applicat * @param taskProperties the task properties used to obtain tablePrefix if not set by * tablePrefix field. */ - public DefaultTaskConfigurer(DataSource dataSource, String tablePrefix, ApplicationContext context, - TaskProperties taskProperties) { + public DefaultTaskConfigurer(@Nullable DataSource dataSource, @Nullable String tablePrefix, + @Nullable ApplicationContext context, @Nullable TaskProperties taskProperties) { this.dataSource = dataSource; this.context = context; @@ -183,7 +186,7 @@ public TaskExplorer getTaskExplorer() { } @Override - public DataSource getTaskDataSource() { + public @Nullable DataSource getTaskDataSource() { return this.dataSource; } @@ -208,6 +211,7 @@ public PlatformTransactionManager getTransactionManager() { } finally { if (this.transactionManager == null) { + Assert.state(this.dataSource != null, "DataSource must be non-null when available"); this.transactionManager = new JdbcTransactionManager(this.dataSource); } } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java index 91a3f9859..c87d74566 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java @@ -24,6 +24,7 @@ import jakarta.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +44,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** @@ -67,7 +69,7 @@ public class SimpleTaskAutoConfiguration { protected static final Log logger = LogFactory.getLog(SimpleTaskAutoConfiguration.class); @Autowired(required = false) - private Collection dataSources; + private @Nullable Collection dataSources; @Autowired private ConfigurableApplicationContext context; @@ -77,33 +79,37 @@ public class SimpleTaskAutoConfiguration { private boolean initialized = false; - private TaskRepository taskRepository; + private @Nullable TaskRepository taskRepository; - private PlatformTransactionManager platformTransactionManager; + private @Nullable PlatformTransactionManager platformTransactionManager; - private TaskExplorer taskExplorer; + private @Nullable TaskExplorer taskExplorer; - private TaskNameResolver taskNameResolver; + private @Nullable TaskNameResolver taskNameResolver; @Bean public SimpleTaskRepository taskRepository() { + Assert.state(this.taskRepository != null, "taskRepository must be initialized"); return (SimpleTaskRepository) this.taskRepository; } @Conditional(NoTransactionManagerProperty.class) @Bean public PlatformTransactionManager springCloudTaskTransactionManager() { + Assert.state(this.platformTransactionManager != null, "platformTransactionManager must be initialized"); return this.platformTransactionManager; } @Bean public TaskExplorer taskExplorer() { + Assert.state(this.taskExplorer != null, "taskExplorer must be initialized"); return this.taskExplorer; } @Bean public TaskNameResolver taskNameResolver() { - return taskNameResolver; + Assert.state(this.taskNameResolver != null, "taskNameResolver must be initialized"); + return this.taskNameResolver; } @Bean diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SingleInstanceTaskListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SingleInstanceTaskListener.java index 9aa7d2374..5f6fa7d61 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SingleInstanceTaskListener.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SingleInstanceTaskListener.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.cloud.task.listener.TaskExecutionException; import org.springframework.cloud.task.listener.annotation.AfterTask; @@ -41,6 +42,7 @@ import org.springframework.integration.support.leader.LockRegistryLeaderInitiator; import org.springframework.integration.support.locks.LockRegistry; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.Assert; /** * When spring.cloud.task.single-instance-enabled is set to true this listener will create @@ -56,9 +58,10 @@ public class SingleInstanceTaskListener implements ApplicationListenerspring.cloud.task.initialize.enable until it * is removed. */ - private Boolean initializeEnabled; + private @Nullable Boolean initializeEnabled; - public String getExternalExecutionId() { + public @Nullable String getExternalExecutionId() { return this.externalExecutionId; } - public void setExternalExecutionId(String externalExecutionId) { + public void setExternalExecutionId(@Nullable String externalExecutionId) { this.externalExecutionId = externalExecutionId; } - public Long getExecutionid() { + public @Nullable Long getExecutionid() { return this.executionid; } - public void setExecutionid(Long executionid) { + public void setExecutionid(@Nullable Long executionid) { this.executionid = executionid; } @@ -128,11 +129,11 @@ public void setTablePrefix(String tablePrefix) { this.tablePrefix = tablePrefix; } - public Long getParentExecutionId() { + public @Nullable Long getParentExecutionId() { return this.parentExecutionId; } - public void setParentExecutionId(Long parentExecutionId) { + public void setParentExecutionId(@Nullable Long parentExecutionId) { this.parentExecutionId = parentExecutionId; } @@ -160,11 +161,11 @@ public void setSingleInstanceLockCheckInterval(int singleInstanceLockCheckInterv this.singleInstanceLockCheckInterval = singleInstanceLockCheckInterval; } - public Boolean isInitializeEnabled() { - return initializeEnabled; + public @Nullable Boolean isInitializeEnabled() { + return this.initializeEnabled; } - public void setInitializeEnabled(Boolean initializeEnabled) { + public void setInitializeEnabled(@Nullable Boolean initializeEnabled) { this.initializeEnabled = initializeEnabled; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskRuntimeHints.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskRuntimeHints.java index 071ea19aa..c194712e2 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskRuntimeHints.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskRuntimeHints.java @@ -18,6 +18,8 @@ import java.sql.Statement; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -34,7 +36,7 @@ public class TaskRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection().registerType(TypeReference.of("java.sql.DatabaseMetaData"), hint -> { }); hints.resources().registerPattern("org/springframework/cloud/task/schema-db2.sql"); diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java index c9997ffad..bc95b43c3 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java @@ -18,6 +18,7 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.ApplicationArguments; @@ -38,9 +39,9 @@ class ObservationApplicationRunner implements ApplicationRunner { private final String beanName; - private ObservationRegistry registry; + private @Nullable ObservationRegistry registry; - private TaskObservationConvention taskObservationConvention; + private @Nullable TaskObservationConvention taskObservationConvention; ObservationApplicationRunner(BeanFactory beanFactory, ApplicationRunner delegate, String beanName) { this.beanFactory = beanFactory; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java index 299bef854..97811159b 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java @@ -18,6 +18,7 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.CommandLineRunner; @@ -37,9 +38,9 @@ class ObservationCommandLineRunner implements CommandLineRunner { private final String beanName; - private ObservationRegistry registry; + private @Nullable ObservationRegistry registry; - private TaskObservationConvention taskObservationConvention; + private @Nullable TaskObservationConvention taskObservationConvention; ObservationCommandLineRunner(BeanFactory beanFactory, CommandLineRunner delegate, String beanName) { this.beanFactory = beanFactory; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/package-info.java index df72d6209..0b9974d16 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/package-info.java @@ -17,4 +17,7 @@ /** * Observation support for Spring Cloud Task configuration. */ +@NullMarked package org.springframework.cloud.task.configuration.observation; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java index 1c8584b59..09340143a 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java @@ -17,4 +17,7 @@ /** * Interfaces for configuring Spring Cloud Task and a default implementations. */ +@NullMarked package org.springframework.cloud.task.configuration; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java index 39c1759ba..b9b7bdf57 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java @@ -30,6 +30,7 @@ import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; @@ -102,8 +103,9 @@ public class TaskLifecycleListener @Autowired(required = false) private ObservationConvention observationConvention; - private List taskExecutionListeners; + private @Nullable List taskExecutionListeners; + @SuppressWarnings("NullAway.Init") private TaskExecution taskExecution; private TaskProperties taskProperties; @@ -114,15 +116,15 @@ public class TaskLifecycleListener private boolean listenerFailed = false; - private Throwable listenerException; + private @Nullable Throwable listenerException; - private TaskNameResolver taskNameResolver; + private final TaskNameResolver taskNameResolver; - private ApplicationArguments applicationArguments; + private final ApplicationArguments applicationArguments; - private Throwable applicationFailedException; + private @Nullable Throwable applicationFailedException; - private ExitCodeEvent exitCodeEvent; + private @Nullable ExitCodeEvent exitCodeEvent; /** * @param taskRepository {@link TaskRepository} to record executions. @@ -203,8 +205,11 @@ private void doTaskEnd() { } setExitMessage(invokeOnTaskEnd(this.taskExecution)); - this.taskRepository.completeTaskExecution(this.taskExecution.getExecutionId(), - this.taskExecution.getExitCode(), this.taskExecution.getEndTime(), + Integer exitCode = this.taskExecution.getExitCode(); + LocalDateTime endTime = this.taskExecution.getEndTime(); + Assert.state(exitCode != null, "exitCode must not be null"); + Assert.state(endTime != null, "endTime must not be null"); + this.taskRepository.completeTaskExecution(this.taskExecution.getExecutionId(), exitCode, endTime, this.taskExecution.getExitMessage(), this.taskExecution.getErrorMessage()); this.finished = true; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskListenerExecutorObjectFactory.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskListenerExecutorObjectFactory.java index 633e2694a..e4f40d1ac 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskListenerExecutorObjectFactory.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskListenerExecutorObjectFactory.java @@ -57,10 +57,13 @@ public class TaskListenerExecutorObjectFactory implements ObjectFactory> beforeTaskInstances; + @SuppressWarnings("NullAway.Init") private Map> afterTaskInstances; + @SuppressWarnings("NullAway.Init") private Map> failedTaskInstances; public TaskListenerExecutorObjectFactory(ConfigurableApplicationContext context) { @@ -76,6 +79,7 @@ public TaskListenerExecutor getObject() { return new TaskListenerExecutor(this.beforeTaskInstances, this.afterTaskInstances, this.failedTaskInstances); } + @SuppressWarnings("NullAway") private void initializeExecutor() { ConfigurableListableBeanFactory factory = this.context.getBeanFactory(); for (String beanName : this.context.getBeanDefinitionNames()) { diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskObservations.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskObservations.java index cc95ef2df..db07401bb 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskObservations.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskObservations.java @@ -19,6 +19,7 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.ObservationRegistry; +import org.jspecify.annotations.Nullable; import org.springframework.cloud.task.configuration.TaskObservationCloudKeyValues; import org.springframework.cloud.task.repository.TaskExecution; @@ -60,12 +61,16 @@ public TaskObservations(ObservationRegistry observationRegistry, this.customObservationConvention = customObservationConvention; } + @SuppressWarnings("NullAway.Init") private Observation.Scope scope; + @SuppressWarnings("NullAway.Init") private TaskExecutionObservationConvention observationsProvider = new DefaultTaskExecutionObservationConvention(); + @SuppressWarnings("NullAway.Init") private TaskExecutionObservationContext taskObservationContext; + @Nullable TaskObservationCloudKeyValues taskObservationCloudKeyValues; public void onTaskStartup(TaskExecution taskExecution) { @@ -107,7 +112,7 @@ public void onTaskStartup(TaskExecution taskExecution) { this.scope = observation.openScope(); } - private String getValueOrDefault(Object value) { + private String getValueOrDefault(@Nullable Object value) { return (value != null) ? value.toString() : UNKNOWN; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/TaskListenerExecutor.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/TaskListenerExecutor.java index 811712dfb..9cebdbfca 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/TaskListenerExecutor.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/TaskListenerExecutor.java @@ -81,7 +81,9 @@ public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) { private void executeTaskListener(TaskExecution taskExecution, Set methods, Map> instances) { for (Method method : methods) { - for (Object instance : instances.get(method)) { + Set instanceSet = instances.get(method); + org.springframework.util.Assert.state(instanceSet != null, "instanceSet must not be null"); + for (Object instance : instanceSet) { try { method.invoke(instance, taskExecution); } @@ -106,7 +108,9 @@ private void executeTaskListener(TaskExecution taskExecution, Set method private void executeTaskListenerWithThrowable(TaskExecution taskExecution, Throwable throwable, Set methods, Map> instances) { for (Method method : methods) { - for (Object instance : instances.get(method)) { + Set instanceSet = instances.get(method); + org.springframework.util.Assert.state(instanceSet != null, "instanceSet must not be null"); + for (Object instance : instanceSet) { try { method.invoke(instance, taskExecution, throwable); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/package-info.java index 1cc3ced7b..0ac78c375 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/package-info.java @@ -17,4 +17,7 @@ /** * Annotation-based listener support for Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.listener.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/package-info.java index c91d25d4e..ae48eb2dd 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/package-info.java @@ -17,4 +17,7 @@ /** * Task lifecycle listener support for Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.listener; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java index 45292b91f..fc6db9541 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java @@ -17,4 +17,7 @@ /** * Base package for spring cloud task. */ +@NullMarked package org.springframework.cloud.task; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExecution.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExecution.java index ec032825b..c80b45ece 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExecution.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExecution.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -40,46 +42,46 @@ public class TaskExecution { /** * The parent task execution id. */ - private Long parentExecutionId; + private @Nullable Long parentExecutionId; /** * The recorded exit code for the task. */ - private Integer exitCode; + private @Nullable Integer exitCode; /** * User defined name for the task. */ - private String taskName; + private @Nullable String taskName; /** * Time of when the task was started. */ - private LocalDateTime startTime; + private @Nullable LocalDateTime startTime; /** * Timestamp of when the task was completed/terminated. */ - private LocalDateTime endTime; + private @Nullable LocalDateTime endTime; /** * Message returned from the task or stacktrace. */ - private String exitMessage; + private @Nullable String exitMessage; /** * Id assigned to the task by the platform. * * @since 1.1.0 */ - private String externalExecutionId; + private @Nullable String externalExecutionId; /** * Error information available upon the failure of a task. * * @since 1.1.0 */ - private String errorMessage; + private @Nullable String errorMessage; /** * The arguments that were used for this task execution. @@ -90,9 +92,10 @@ public TaskExecution() { this.arguments = new ArrayList<>(); } - public TaskExecution(long executionId, Integer exitCode, String taskName, LocalDateTime startTime, - LocalDateTime endTime, String exitMessage, List arguments, String errorMessage, - String externalExecutionId, Long parentExecutionId) { + public TaskExecution(long executionId, @Nullable Integer exitCode, @Nullable String taskName, + @Nullable LocalDateTime startTime, @Nullable LocalDateTime endTime, @Nullable String exitMessage, + List arguments, @Nullable String errorMessage, @Nullable String externalExecutionId, + @Nullable Long parentExecutionId) { Assert.notNull(arguments, "arguments must not be null"); this.executionId = executionId; @@ -107,9 +110,9 @@ public TaskExecution(long executionId, Integer exitCode, String taskName, LocalD this.parentExecutionId = parentExecutionId; } - public TaskExecution(long executionId, Integer exitCode, String taskName, LocalDateTime startTime, - LocalDateTime endTime, String exitMessage, List arguments, String errorMessage, - String externalExecutionId) { + public TaskExecution(long executionId, @Nullable Integer exitCode, @Nullable String taskName, + @Nullable LocalDateTime startTime, @Nullable LocalDateTime endTime, @Nullable String exitMessage, + List arguments, @Nullable String errorMessage, @Nullable String externalExecutionId) { this(executionId, exitCode, taskName, startTime, endTime, exitMessage, arguments, errorMessage, externalExecutionId, null); @@ -119,43 +122,43 @@ public long getExecutionId() { return this.executionId; } - public Integer getExitCode() { + public @Nullable Integer getExitCode() { return this.exitCode; } - public void setExitCode(Integer exitCode) { + public void setExitCode(@Nullable Integer exitCode) { this.exitCode = exitCode; } - public String getTaskName() { + public @Nullable String getTaskName() { return this.taskName; } - public void setTaskName(String taskName) { + public void setTaskName(@Nullable String taskName) { this.taskName = taskName; } - public LocalDateTime getStartTime() { + public @Nullable LocalDateTime getStartTime() { return this.startTime; } - public void setStartTime(LocalDateTime startTime) { + public void setStartTime(@Nullable LocalDateTime startTime) { this.startTime = startTime; } - public LocalDateTime getEndTime() { + public @Nullable LocalDateTime getEndTime() { return this.endTime; } - public void setEndTime(LocalDateTime endTime) { + public void setEndTime(@Nullable LocalDateTime endTime) { this.endTime = endTime; } - public String getExitMessage() { + public @Nullable String getExitMessage() { return this.exitMessage; } - public void setExitMessage(String exitMessage) { + public void setExitMessage(@Nullable String exitMessage) { this.exitMessage = exitMessage; } @@ -167,27 +170,27 @@ public void setArguments(List arguments) { this.arguments = new ArrayList<>(arguments); } - public String getErrorMessage() { + public @Nullable String getErrorMessage() { return this.errorMessage; } - public void setErrorMessage(String errorMessage) { + public void setErrorMessage(@Nullable String errorMessage) { this.errorMessage = errorMessage; } - public String getExternalExecutionId() { + public @Nullable String getExternalExecutionId() { return this.externalExecutionId; } - public void setExternalExecutionId(String externalExecutionId) { + public void setExternalExecutionId(@Nullable String externalExecutionId) { this.externalExecutionId = externalExecutionId; } - public Long getParentExecutionId() { + public @Nullable Long getParentExecutionId() { return this.parentExecutionId; } - public void setParentExecutionId(Long parentExecutionId) { + public void setParentExecutionId(@Nullable Long parentExecutionId) { this.parentExecutionId = parentExecutionId; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java index 13f05a178..8f5fd96f9 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -36,6 +38,7 @@ public interface TaskExplorer { * @param executionId the task execution id * @return the {@link TaskExecution} with this id, or null if not found */ + @Nullable TaskExecution getTaskExecution(long executionId); /** @@ -109,6 +112,7 @@ public interface TaskExplorer { * @param jobExecutionId the id of the JobExecution * @return the id of the {@link TaskExecution} */ + @Nullable Long getTaskExecutionIdByJobExecutionId(long jobExecutionId); /** @@ -147,6 +151,7 @@ public interface TaskExplorer { * @return The latest Task Execution or null * @see #getLatestTaskExecutionsByTaskNames(String...) */ + @Nullable TaskExecution getLatestTaskExecutionForTaskName(String taskName); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskNameResolver.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskNameResolver.java index ae7a544ad..f2149a8fb 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskNameResolver.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskNameResolver.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.repository; +import org.jspecify.annotations.Nullable; + /** * Strategy interface for customizing how the name of a task is determined. * @@ -26,6 +28,7 @@ public interface TaskNameResolver { /** * @return the name of the task being executed within this context. */ + @Nullable String getTaskName(); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskRepository.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskRepository.java index 3d2afa811..dceb8845c 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskRepository.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskRepository.java @@ -19,6 +19,8 @@ import java.time.LocalDateTime; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.transaction.annotation.Transactional; /** @@ -40,7 +42,9 @@ public interface TaskRepository { * @return the updated {@link TaskExecution} */ @Transactional("${spring.cloud.task.transaction-manager:springCloudTaskTransactionManager}") - TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage); + @Nullable + TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage); /** * Notifies the repository that a taskExecution has completed. @@ -53,8 +57,9 @@ public interface TaskRepository { * @since 1.1.0 */ @Transactional("${spring.cloud.task.transaction-manager:springCloudTaskTransactionManager}") - TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage, - String errorMessage); + @Nullable + TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage, @Nullable String errorMessage); /** * Notifies the repository that a taskExecution needs to be created. @@ -77,7 +82,7 @@ TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDat * @return the initial {@link TaskExecution} */ @Transactional("${spring.cloud.task.transaction-manager:springCloudTaskTransactionManager}") - TaskExecution createTaskExecution(String name); + TaskExecution createTaskExecution(@Nullable String name); /** * Creates an empty TaskExecution with just an id provided. This is intended to be @@ -122,7 +127,7 @@ TaskExecution startTaskExecution(long executionid, String taskName, LocalDateTim * a TaskExecution. */ @Transactional("${spring.cloud.task.transaction-manager:springCloudTaskTransactionManager}") - TaskExecution startTaskExecution(long executionid, String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId, Long parentExecutionId); + TaskExecution startTaskExecution(long executionid, @Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId, @Nullable Long parentExecutionId); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java index 86bd11ac5..f3eda8c47 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java @@ -33,6 +33,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.database.Order; import org.springframework.cloud.task.configuration.TaskProperties; import org.springframework.cloud.task.repository.TaskExecution; @@ -181,6 +183,7 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { private LinkedHashMap orderMap; + @SuppressWarnings("NullAway.Init") private DataFieldMaxValueIncrementer taskIncrementer; /** @@ -209,14 +212,14 @@ public JdbcTaskExecutionDao(DataSource dataSource) { } @Override - public TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId) { + public TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId) { return createTaskExecution(taskName, startTime, arguments, externalExecutionId, null); } @Override - public TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId, Long parentExecutionId) { + public TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId, @Nullable Long parentExecutionId) { long nextExecutionId = getNextExecutionId(); TaskExecution taskExecution = new TaskExecution(nextExecutionId, null, taskName, startTime, null, null, @@ -243,8 +246,9 @@ public TaskExecution startTaskExecution(long executionId, String taskName, Local } @Override - public TaskExecution startTaskExecution(long executionId, String taskName, LocalDateTime startTime, - List arguments, String externalExecutionId, Long parentExecutionId) { + public TaskExecution startTaskExecution(long executionId, @Nullable String taskName, + @Nullable LocalDateTime startTime, List arguments, @Nullable String externalExecutionId, + @Nullable Long parentExecutionId) { TaskExecution taskExecution = new TaskExecution(executionId, null, taskName, startTime, null, null, arguments, null, externalExecutionId, parentExecutionId); @@ -272,15 +276,16 @@ public TaskExecution startTaskExecution(long executionId, String taskName, Local } @Override - public void completeTaskExecution(long taskExecutionId, Integer exitCode, LocalDateTime endTime, String exitMessage, - String errorMessage) { + public void completeTaskExecution(long taskExecutionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage, @Nullable String errorMessage) { final MapSqlParameterSource queryParameters = new MapSqlParameterSource().addValue("taskExecutionId", taskExecutionId, Types.BIGINT); // Check if given TaskExecution's Id already exists, if none is found // it is invalid and an exception should be thrown. - if (this.jdbcTemplate.queryForObject(getQuery(CHECK_TASK_EXECUTION_EXISTS), queryParameters, - Integer.class) != 1) { + Integer count = this.jdbcTemplate.queryForObject(getQuery(CHECK_TASK_EXECUTION_EXISTS), queryParameters, + Integer.class); + if (count == null || count != 1) { throw new IllegalStateException("Invalid TaskExecution, ID " + taskExecutionId + " not found."); } @@ -297,18 +302,19 @@ public void completeTaskExecution(long taskExecutionId, Integer exitCode, LocalD @Override public void completeTaskExecution(long taskExecutionId, Integer exitCode, LocalDateTime endTime, - String exitMessage) { + @Nullable String exitMessage) { completeTaskExecution(taskExecutionId, exitCode, endTime, exitMessage, null); } @Override - public TaskExecution getTaskExecution(long executionId) { + public @Nullable TaskExecution getTaskExecution(long executionId) { final MapSqlParameterSource queryParameters = new MapSqlParameterSource().addValue("taskExecutionId", executionId, Types.BIGINT); try { TaskExecution taskExecution = this.jdbcTemplate.queryForObject(getQuery(GET_EXECUTION_BY_ID), queryParameters, new TaskExecutionRowMapper()); + Assert.state(taskExecution != null, "TaskExecution must not be null"); taskExecution.setArguments(getTaskArguments(executionId)); return taskExecution; } @@ -324,8 +330,9 @@ public long getTaskExecutionCountByTaskName(String taskName) { Types.VARCHAR); try { - return this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT_BY_NAME), queryParameters, + Long count = this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT_BY_NAME), queryParameters, Long.class); + return count != null ? count : 0; } catch (EmptyResultDataAccessException e) { return 0; @@ -338,8 +345,9 @@ public long getRunningTaskExecutionCountByTaskName(String taskName) { Types.VARCHAR); try { - return this.jdbcTemplate.queryForObject(getQuery(RUNNING_TASK_EXECUTION_COUNT_BY_NAME), queryParameters, - Long.class); + Long count = this.jdbcTemplate.queryForObject(getQuery(RUNNING_TASK_EXECUTION_COUNT_BY_NAME), + queryParameters, Long.class); + return count != null ? count : 0; } catch (EmptyResultDataAccessException e) { return 0; @@ -351,8 +359,9 @@ public long getRunningTaskExecutionCount() { try { final MapSqlParameterSource queryParameters = new MapSqlParameterSource(); - return this.jdbcTemplate.queryForObject(getQuery(RUNNING_TASK_EXECUTION_COUNT), queryParameters, + Long count = this.jdbcTemplate.queryForObject(getQuery(RUNNING_TASK_EXECUTION_COUNT), queryParameters, Long.class); + return count != null ? count : 0; } catch (EmptyResultDataAccessException e) { return 0; @@ -386,7 +395,7 @@ public List getLatestTaskExecutionsByTaskNames(String... taskName } @Override - public TaskExecution getLatestTaskExecutionForTaskName(String taskName) { + public @Nullable TaskExecution getLatestTaskExecutionForTaskName(String taskName) { Assert.hasText(taskName, "The task name must not be empty."); final List taskExecutions = this.getLatestTaskExecutionsByTaskNames(taskName); if (taskExecutions.isEmpty()) { @@ -405,8 +414,9 @@ else if (taskExecutions.size() == 1) { public long getTaskExecutionCount() { try { - return this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT), new MapSqlParameterSource(), + Long count = this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT), new MapSqlParameterSource(), Long.class); + return count != null ? count : 0; } catch (EmptyResultDataAccessException e) { return 0; @@ -432,8 +442,9 @@ public long getTaskExecutionCountByExternalExecutionId(String externalExecutionI externalExecutionId, Types.VARCHAR); try { - return this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT_BY_EXTERNAL_EXECUTION_ID), + Long count = this.jdbcTemplate.queryForObject(getQuery(TASK_EXECUTION_COUNT_BY_EXTERNAL_EXECUTION_ID), queryParameters, Long.class); + return count != null ? count : 0; } catch (EmptyResultDataAccessException e) { return 0; @@ -447,6 +458,7 @@ public Page findTaskExecutionsByName(String taskName, Pageable pa } @Override + @SuppressWarnings("NullAway") public List getTaskNames() { return this.jdbcTemplate.queryForList(getQuery(FIND_TASK_NAMES), new MapSqlParameterSource(), String.class); } @@ -466,13 +478,14 @@ public long getNextExecutionId() { } @Override - public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + public @Nullable Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { final MapSqlParameterSource queryParameters = new MapSqlParameterSource().addValue("jobExecutionId", jobExecutionId, Types.BIGINT); try { - return this.jdbcTemplate.queryForObject(getQuery(FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID), queryParameters, - Long.class); + Long taskExecutionId = this.jdbcTemplate.queryForObject(getQuery(FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID), + queryParameters, Long.class); + return taskExecutionId; } catch (EmptyResultDataAccessException e) { return null; @@ -485,8 +498,8 @@ public Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId) { taskExecutionId, Types.BIGINT); try { - return this.jdbcTemplate.query(getQuery(FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID), queryParameters, - new ResultSetExtractor>() { + Set result = this.jdbcTemplate.query(getQuery(FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID), + queryParameters, new ResultSetExtractor>() { @Override public Set extractData(ResultSet resultSet) throws SQLException, DataAccessException { Set jobExecutionIds = new TreeSet<>(); @@ -498,6 +511,7 @@ public Set extractData(ResultSet resultSet) throws SQLException, DataAcces return jobExecutionIds; } }); + return result != null ? result : Collections.emptySet(); } catch (DataAccessException e) { return Collections.emptySet(); @@ -516,7 +530,7 @@ public void updateExternalExecutionId(long taskExecutionId, String externalExecu } private Page queryForPageableResults(Pageable pageable, String selectClause, String fromClause, - String whereClause, MapSqlParameterSource queryParameters, long totalCount) { + @Nullable String whereClause, MapSqlParameterSource queryParameters, long totalCount) { SqlPagingQueryProviderFactoryBean factoryBean = new SqlPagingQueryProviderFactoryBean(); factoryBean.setSelectClause(selectClause); factoryBean.setFromClause(fromClause); @@ -624,7 +638,8 @@ public TaskExecution mapRow(ResultSet rs, int rowNum) throws SQLException { startTime = rs.getObject("START_TIME", LocalDateTime.class); } catch (NullPointerException npe) { - if (!npe.getMessage().contains("")) { + String message = npe.getMessage(); + if (message == null || !message.contains("")) { throw npe; } } @@ -633,7 +648,8 @@ public TaskExecution mapRow(ResultSet rs, int rowNum) throws SQLException { endTime = rs.getObject("END_TIME", LocalDateTime.class); } catch (NullPointerException npe) { - if (!npe.getMessage().contains("")) { + String message = npe.getMessage(); + if (message == null || !message.contains("")) { throw npe; } } @@ -642,7 +658,7 @@ public TaskExecution mapRow(ResultSet rs, int rowNum) throws SQLException { rs.getString("EXTERNAL_EXECUTION_ID"), parentExecutionId); } - private Integer getNullableExitCode(ResultSet rs) throws SQLException { + private @Nullable Integer getNullableExitCode(ResultSet rs) throws SQLException { int exitCode = rs.getInt("EXIT_CODE"); return !rs.wasNull() ? exitCode : null; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java index b6087caac..6dde6630b 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java @@ -30,6 +30,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -58,14 +60,14 @@ public MapTaskExecutionDao() { } @Override - public TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId) { + public TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId) { return createTaskExecution(taskName, startTime, arguments, externalExecutionId, null); } @Override - public TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId, Long parentExecutionId) { + public TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId, @Nullable Long parentExecutionId) { long taskExecutionId = getNextExecutionId(); TaskExecution taskExecution = new TaskExecution(taskExecutionId, null, taskName, startTime, null, null, arguments, null, externalExecutionId, parentExecutionId); @@ -80,9 +82,11 @@ public TaskExecution startTaskExecution(long executionId, String taskName, Local } @Override - public TaskExecution startTaskExecution(long executionId, String taskName, LocalDateTime startTime, - List arguments, String externalExecutionid, Long parentExecutionId) { + public TaskExecution startTaskExecution(long executionId, @Nullable String taskName, + @Nullable LocalDateTime startTime, List arguments, @Nullable String externalExecutionid, + @Nullable Long parentExecutionId) { TaskExecution taskExecution = this.taskExecutions.get(executionId); + Assert.state(taskExecution != null, "TaskExecution with ID " + executionId + " not found."); taskExecution.setTaskName(taskName); taskExecution.setStartTime(startTime); taskExecution.setArguments(arguments); @@ -94,8 +98,8 @@ public TaskExecution startTaskExecution(long executionId, String taskName, Local } @Override - public void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage, - String errorMessage) { + public void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage, @Nullable String errorMessage) { if (!this.taskExecutions.containsKey(executionId)) { throw new IllegalStateException("Invalid TaskExecution, ID " + executionId + " not found."); } @@ -108,12 +112,13 @@ public void completeTaskExecution(long executionId, Integer exitCode, LocalDateT } @Override - public void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage) { + public void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage) { completeTaskExecution(executionId, exitCode, endTime, exitMessage, null); } @Override - public TaskExecution getTaskExecution(long executionId) { + public @Nullable TaskExecution getTaskExecution(long executionId) { return this.taskExecutions.get(executionId); } @@ -121,7 +126,8 @@ public TaskExecution getTaskExecution(long executionId) { public long getTaskExecutionCountByTaskName(String taskName) { int count = 0; for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getTaskName().equals(taskName)) { + String entryTaskName = entry.getValue().getTaskName(); + if (entryTaskName != null && entryTaskName.equals(taskName)) { count++; } } @@ -132,7 +138,8 @@ public long getTaskExecutionCountByTaskName(String taskName) { public long getTaskExecutionCountByExternalExecutionId(String externalExecutionId) { int count = 0; for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getExternalExecutionId().equals(externalExecutionId)) { + String entryExternalId = entry.getValue().getExternalExecutionId(); + if (entryExternalId != null && entryExternalId.equals(externalExecutionId)) { count++; } } @@ -143,7 +150,8 @@ public long getTaskExecutionCountByExternalExecutionId(String externalExecutionI public long getRunningTaskExecutionCountByTaskName(String taskName) { int count = 0; for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getTaskName().equals(taskName) && entry.getValue().getEndTime() == null) { + String entryTaskName = entry.getValue().getTaskName(); + if (entryTaskName != null && entryTaskName.equals(taskName) && entry.getValue().getEndTime() == null) { count++; } } @@ -170,7 +178,8 @@ public long getTaskExecutionCount() { public Page findRunningTaskExecutions(String taskName, Pageable pageable) { Set result = getTaskExecutionTreeSet(); for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getTaskName().equals(taskName) && entry.getValue().getEndTime() == null) { + String entryTaskName = entry.getValue().getTaskName(); + if (entryTaskName != null && entryTaskName.equals(taskName) && entry.getValue().getEndTime() == null) { result.add(entry.getValue()); } } @@ -181,7 +190,8 @@ public Page findRunningTaskExecutions(String taskName, Pageable p public Page findTaskExecutionsByExternalExecutionId(String externalExecutionId, Pageable pageable) { Set result = getTaskExecutionTreeSet(); for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getExternalExecutionId().equals(externalExecutionId)) { + String entryExternalId = entry.getValue().getExternalExecutionId(); + if (entryExternalId != null && entryExternalId.equals(externalExecutionId)) { result.add(entry.getValue()); } } @@ -193,7 +203,8 @@ public Page findTaskExecutionsByExternalExecutionId(String extern public Page findTaskExecutionsByName(String taskName, Pageable pageable) { Set filteredSet = getTaskExecutionTreeSet(); for (Map.Entry entry : this.taskExecutions.entrySet()) { - if (entry.getValue().getTaskName().equals(taskName)) { + String entryTaskName = entry.getValue().getTaskName(); + if (entryTaskName != null && entryTaskName.equals(taskName)) { filteredSet.add(entry.getValue()); } } @@ -226,7 +237,7 @@ public long getNextExecutionId() { } @Override - public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + public @Nullable Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { Long taskId = null; found: @@ -268,7 +279,11 @@ private TreeSet getTaskExecutionTreeSet() { return new TreeSet<>(new Comparator() { @Override public int compare(TaskExecution e1, TaskExecution e2) { - int result = e1.getStartTime().compareTo(e2.getStartTime()); + LocalDateTime startTime1 = e1.getStartTime(); + LocalDateTime startTime2 = e2.getStartTime(); + Assert.state(startTime1 != null, "TaskExecution start time must not be null"); + Assert.state(startTime2 != null, "TaskExecution start time must not be null"); + int result = startTime1.compareTo(startTime2); if (result == 0) { result = Long.valueOf(e1.getExecutionId()).compareTo(e2.getExecutionId()); } @@ -284,6 +299,7 @@ private Page getPageFromList(List executionList, Pageable pageabl } @Override + @SuppressWarnings("NullAway") public List getLatestTaskExecutionsByTaskNames(String... taskNames) { Assert.notEmpty(taskNames, "At least 1 task name must be provided."); @@ -324,7 +340,7 @@ public List getLatestTaskExecutionsByTaskNames(String... taskName } @Override - public TaskExecution getLatestTaskExecutionForTaskName(String taskName) { + public @Nullable TaskExecution getLatestTaskExecutionForTaskName(String taskName) { Assert.hasText(taskName, "The task name must not be empty."); final List taskExecutions = this.getLatestTaskExecutionsByTaskNames(taskName); if (taskExecutions.isEmpty()) { @@ -343,11 +359,15 @@ private static final class TaskExecutionComparator implements Comparator arguments, - String externalExecutionId); + TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId); /** * Save a new {@link TaskExecution}. @@ -55,8 +57,8 @@ TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List * @return A fully qualified {@link TaskExecution} instance. * @since 1.2.0 */ - TaskExecution createTaskExecution(String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId, Long parentExecutionId); + TaskExecution createTaskExecution(@Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId, @Nullable Long parentExecutionId); /** * Update and existing {@link TaskExecution} to mark it as started. @@ -84,8 +86,8 @@ TaskExecution startTaskExecution(long executionId, String taskName, LocalDateTim * start. * @since 1.2.0 */ - TaskExecution startTaskExecution(long executionId, String taskName, LocalDateTime startTime, List arguments, - String externalExecutionId, Long parentExecutionId); + TaskExecution startTaskExecution(long executionId, @Nullable String taskName, @Nullable LocalDateTime startTime, + List arguments, @Nullable String externalExecutionId, @Nullable Long parentExecutionId); /** * Update and existing {@link TaskExecution} to mark it as completed. @@ -96,8 +98,8 @@ TaskExecution startTaskExecution(long executionId, String taskName, LocalDateTim * @param errorMessage error information available upon failure of a task. * @since 1.1.0 */ - void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage, - String errorMessage); + void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, @Nullable String exitMessage, + @Nullable String errorMessage); /** * Update and existing {@link TaskExecution}. @@ -106,13 +108,14 @@ void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime end * @param endTime the time the task completed. * @param exitMessage the message assigned to the task upon completion. */ - void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, String exitMessage); + void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, @Nullable String exitMessage); /** * Retrieves a task execution from the task repository. * @param executionId the id associated with the task execution. * @return a fully qualified TaskExecution instance. */ + @Nullable TaskExecution getTaskExecution(long executionId); /** @@ -203,6 +206,7 @@ void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime end * @param jobExecutionId the id of the JobExecution * @return the id of the {@link TaskExecution} */ + @Nullable Long getTaskExecutionIdByJobExecutionId(long jobExecutionId); /** @@ -247,6 +251,7 @@ void completeTaskExecution(long executionId, Integer exitCode, LocalDateTime end * @return The latest Task Execution or null * @see #getLatestTaskExecutionsByTaskNames(String...) */ + @Nullable TaskExecution getLatestTaskExecutionForTaskName(String taskName); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java index cdc9ec673..8d05957cd 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java @@ -18,4 +18,7 @@ * Interface DAO and default implementations for storing and retrieving data for tasks * from a repository. */ +@NullMarked package org.springframework.cloud.task.repository.dao; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/package-info.java index 7833e66b0..69490599e 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/package-info.java @@ -17,4 +17,7 @@ /** * Database-specific components for Spring Cloud Task repository. */ +@NullMarked package org.springframework.cloud.task.repository.database; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java index edef0ab4c..96dc0594f 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java @@ -24,6 +24,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.database.JdbcParameterUtils; import org.springframework.batch.infrastructure.item.database.Order; import org.springframework.cloud.task.repository.database.PagingQueryProvider; @@ -46,11 +48,13 @@ */ public abstract class AbstractSqlPagingQueryProvider implements PagingQueryProvider { + @SuppressWarnings("NullAway.Init") private String selectClause; + @SuppressWarnings("NullAway.Init") private String fromClause; - private String whereClause; + private @Nullable String whereClause; private Map sortKeys = new LinkedHashMap<>(); @@ -89,14 +93,14 @@ public void setFromClause(String fromClause) { /** * @return SQL WHERE clause part of SQL query string */ - protected String getWhereClause() { + protected @Nullable String getWhereClause() { return this.whereClause; } /** * @param whereClause WHERE clause part of SQL query string */ - public void setWhereClause(String whereClause) { + public void setWhereClause(@Nullable String whereClause) { if (StringUtils.hasText(whereClause)) { this.whereClause = removeKeyWord("where", whereClause); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java index 9e373268f..850eca465 100755 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java @@ -22,6 +22,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.database.Order; import org.springframework.beans.factory.FactoryBean; import org.springframework.cloud.task.repository.database.PagingQueryProvider; @@ -51,16 +53,20 @@ */ public class SqlPagingQueryProviderFactoryBean implements FactoryBean { + @SuppressWarnings("NullAway.Init") private DataSource dataSource; - private String databaseType; + private @Nullable String databaseType; + @SuppressWarnings("NullAway.Init") private String fromClause; - private String whereClause; + private @Nullable String whereClause; + @SuppressWarnings("NullAway.Init") private String selectClause; + @SuppressWarnings("NullAway.Init") private Map sortKeys; private Map providers = new HashMap<>(); diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/package-info.java index 524b0076c..5cdfec580 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/package-info.java @@ -17,4 +17,7 @@ /** * Support classes for database-specific Spring Cloud Task repository implementations. */ +@NullMarked package org.springframework.cloud.task.repository.database.support; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/package-info.java index f5dd6a9ff..779fd5146 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/package-info.java @@ -17,4 +17,7 @@ /** * Core repository interfaces and classes for Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.repository; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java index 009665290..b224b415b 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java @@ -23,6 +23,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.jdbc.support.DatabaseMetaDataCallback; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; @@ -114,7 +116,8 @@ public enum DatabaseType { * @return DatabaseType The database type associated with the datasource. * @throws MetaDataAccessException thrown if failure occurs on metadata lookup. */ - public static DatabaseType fromMetaData(DataSource dataSource) throws SQLException, MetaDataAccessException { + public static @Nullable DatabaseType fromMetaData(DataSource dataSource) + throws SQLException, MetaDataAccessException { String databaseProductName = JdbcUtils.extractDatabaseMetaData(dataSource, new DatabaseMetaDataCallback() { @Override @@ -163,7 +166,7 @@ else if (databaseProductName.indexOf("AS") != -1 && (databaseProductVersion.star * @return DatabaseType for given product name. * @throws IllegalArgumentException if none is found. */ - public static DatabaseType fromProductName(String productName) { + public static DatabaseType fromProductName(@Nullable String productName) { if (!dbNameMap.containsKey(productName)) { throw new IllegalArgumentException("DatabaseType not found for product name: [" + productName + "]"); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java index 4cc5d8a7f..2c0222fb1 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.cloud.task.repository.dao.TaskExecutionDao; @@ -50,7 +52,7 @@ public SimpleTaskExplorer(TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBea } @Override - public TaskExecution getTaskExecution(long executionId) { + public @Nullable TaskExecution getTaskExecution(long executionId) { return this.taskExecutionDao.getTaskExecution(executionId); } @@ -100,7 +102,7 @@ public Page findAll(Pageable pageable) { } @Override - public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + public @Nullable Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { return this.taskExecutionDao.getTaskExecutionIdByJobExecutionId(jobExecutionId); } @@ -115,7 +117,7 @@ public List getLatestTaskExecutionsByTaskNames(String... taskName } @Override - public TaskExecution getLatestTaskExecutionForTaskName(String taskName) { + public @Nullable TaskExecution getLatestTaskExecutionForTaskName(String taskName) { return this.taskExecutionDao.getLatestTaskExecutionForTaskName(taskName); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskNameResolver.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskNameResolver.java index 7709f5d49..1364623ff 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskNameResolver.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskNameResolver.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.repository.support; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.task.repository.TaskNameResolver; @@ -36,8 +38,10 @@ */ public class SimpleTaskNameResolver implements TaskNameResolver, ApplicationContextAware { + @SuppressWarnings("NullAway.Init") private ApplicationContext context; + @SuppressWarnings("NullAway.Init") private String configuredName; @Value("${spring.cloud.task.name:}") @@ -51,12 +55,12 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } @Override - public String getTaskName() { + public @Nullable String getTaskName() { if (StringUtils.hasText(this.configuredName)) { return this.configuredName; } else { - return this.context.getId().replace(":", "_"); + return (this.context.getId() != null) ? this.context.getId().replace(":", "_") : null; } } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java index 3ae4febf4..04420744e 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java @@ -19,9 +19,11 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.FactoryBean; import org.springframework.cloud.task.repository.TaskExecution; @@ -53,6 +55,7 @@ public class SimpleTaskRepository implements TaskRepository { private static final Log logger = LogFactory.getLog(SimpleTaskRepository.class); + @SuppressWarnings("NullAway.Init") private TaskExecutionDao taskExecutionDao; private FactoryBean taskExecutionDaoFactoryBean; @@ -87,19 +90,21 @@ public SimpleTaskRepository(FactoryBean taskExecutionDaoFactor } @Override - public TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, - String exitMessage) { + public @Nullable TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage) { return completeTaskExecution(executionId, exitCode, endTime, exitMessage, null); } @Override - public TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, - String exitMessage, String errorMessage) { + public @Nullable TaskExecution completeTaskExecution(long executionId, Integer exitCode, LocalDateTime endTime, + @Nullable String exitMessage, @Nullable String errorMessage) { initialize(); validateCompletedTaskExitInformation(executionId, exitCode, endTime); exitMessage = trimMessage(exitMessage, this.maxExitMessageSize); - errorMessage = trimMessage(errorMessage, this.maxErrorMessageSize); + if (errorMessage != null) { + errorMessage = trimMessage(errorMessage, this.maxErrorMessageSize); + } this.taskExecutionDao.completeTaskExecution(executionId, exitCode, endTime, exitMessage, errorMessage); logger.debug("Updating: TaskExecution with executionId=" + executionId + " with the following {" + "exitCode=" + exitCode + ", endTime=" + endTime + ", exitMessage='" + exitMessage + '\'' + ", errorMessage='" @@ -120,7 +125,7 @@ public TaskExecution createTaskExecution(TaskExecution taskExecution) { } @Override - public TaskExecution createTaskExecution(String name) { + public TaskExecution createTaskExecution(@Nullable String name) { initialize(); TaskExecution taskExecution = this.taskExecutionDao.createTaskExecution(name, null, Collections.emptyList(), null); @@ -146,8 +151,9 @@ public void updateExternalExecutionId(long executionid, String externalExecution } @Override - public TaskExecution startTaskExecution(long executionid, String taskName, LocalDateTime startTime, - List arguments, String externalExecutionId, Long parentExecutionId) { + public TaskExecution startTaskExecution(long executionid, @Nullable String taskName, + @Nullable LocalDateTime startTime, List arguments, @Nullable String externalExecutionId, + @Nullable Long parentExecutionId) { initialize(); TaskExecution taskExecution = this.taskExecutionDao.startTaskExecution(executionid, taskName, startTime, arguments, externalExecutionId, parentExecutionId); @@ -167,7 +173,8 @@ public TaskExecutionDao getTaskExecutionDao() { private void initialize() { if (!this.initialized) { try { - this.taskExecutionDao = this.taskExecutionDaoFactoryBean.getObject(); + this.taskExecutionDao = Objects.requireNonNull(this.taskExecutionDaoFactoryBean.getObject(), + "taskexecutionDaoFactoryBean object is required"); this.initialized = true; } catch (Exception e) { @@ -194,7 +201,7 @@ private void validateCompletedTaskExitInformation(long executionId, Integer exit Assert.notNull(endTime, "TaskExecution endTime cannot be null."); } - private String trimMessage(String exitMessage, int maxSize) { + private @Nullable String trimMessage(@Nullable String exitMessage, int maxSize) { String result = exitMessage; if (exitMessage != null && exitMessage.length() > maxSize) { result = exitMessage.substring(0, maxSize); diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBean.java index f84920b0f..46d7706d0 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBean.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBean.java @@ -22,6 +22,8 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.infrastructure.item.database.support.DataFieldMaxValueIncrementerFactory; import org.springframework.batch.infrastructure.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; import org.springframework.beans.factory.FactoryBean; @@ -45,9 +47,9 @@ */ public class TaskExecutionDaoFactoryBean implements FactoryBean { - private DataSource dataSource; + private @Nullable DataSource dataSource; - private TaskExecutionDao dao = null; + private @Nullable TaskExecutionDao dao; private String tablePrefix = TaskProperties.DEFAULT_TABLE_PREFIX; @@ -80,6 +82,7 @@ public TaskExecutionDaoFactoryBean(DataSource dataSource) { } @Override + @SuppressWarnings("NullAway") public TaskExecutionDao getObject() throws Exception { if (this.dao == null) { if (this.dataSource != null) { @@ -92,7 +95,9 @@ public TaskExecutionDao getObject() throws Exception { if (this.dataSource != null) { String databaseType = null; try { - databaseType = DatabaseType.fromMetaData(dataSource).name(); + DatabaseType dbType = DatabaseType.fromMetaData(dataSource); + Assert.state(dbType != null, "DatabaseType must not be null"); + databaseType = dbType.name(); } catch (MetaDataAccessException e) { throw new IllegalStateException(e); @@ -128,7 +133,9 @@ private void buildTaskExecutionDao(DataSource dataSource) { this.dao = new JdbcTaskExecutionDao(dataSource, this.tablePrefix); String databaseType; try { - databaseType = DatabaseType.fromMetaData(dataSource).name(); + DatabaseType dbType = DatabaseType.fromMetaData(dataSource); + Assert.state(dbType != null, "DatabaseType must not be null"); + databaseType = dbType.name(); } catch (MetaDataAccessException e) { throw new IllegalStateException(e); @@ -140,6 +147,7 @@ private void buildTaskExecutionDao(DataSource dataSource) { .setTaskIncrementer(incrementerFactory.getIncrementer(databaseType, this.tablePrefix + "SEQ")); } + @SuppressWarnings("NullAway") private boolean isSqlServerTableSequenceAvailable(String incrementerName) { boolean result = false; DatabaseMetaData metaData = null; @@ -155,7 +163,8 @@ private boolean isSqlServerTableSequenceAvailable(String incrementerName) { } } catch (SQLException sqe) { - throw new TaskException(sqe.getMessage()); + String message = sqe.getMessage(); + throw new TaskException(message != null ? message : "SQL error occurred"); } return result; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskRepositoryInitializer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskRepositoryInitializer.java index a7e99bf3d..5f77ee124 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskRepositoryInitializer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskRepositoryInitializer.java @@ -18,6 +18,7 @@ import java.sql.SQLException; import java.util.Locale; +import java.util.Objects; import javax.sql.DataSource; @@ -61,8 +62,10 @@ public final class TaskRepositoryInitializer implements InitializingBean { */ private static String schema = DEFAULT_SCHEMA_LOCATION; + @SuppressWarnings("NullAway.Init") private DataSource dataSource; + @SuppressWarnings("NullAway.Init") private ResourceLoader resourceLoader; @Value("${spring.cloud.task.initialize.enable:true}") @@ -85,7 +88,10 @@ public void setResourceLoader(ResourceLoader resourceLoader) { private String getDatabaseType(DataSource dataSource) { try { - return JdbcUtils.commonDatabaseName(DatabaseType.fromMetaData(dataSource).toString()) + DatabaseType databaseType = DatabaseType.fromMetaData(dataSource); + String databaseTypeName = (databaseType != null) ? databaseType.toString() : null; + return Objects + .requireNonNull(JdbcUtils.commonDatabaseName(databaseTypeName), "Common database name is required") .toLowerCase(Locale.ROOT); } catch (MetaDataAccessException ex) { diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java index 74434a95f..5778354bc 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java @@ -17,4 +17,7 @@ /** * Classes used for setting up and supporting a task repositories. */ +@NullMarked package org.springframework.cloud.task.repository.support; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemProcessListener.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemProcessListener.java index dccec1d6c..a4cf36e4b 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemProcessListener.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemProcessListener.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.batch.core.listener.ItemProcessListener; import org.springframework.batch.infrastructure.item.ItemProcessor; @@ -69,7 +70,7 @@ public void beforeProcess(Object item) { } @Override - public void afterProcess(Object item, Object result) { + public void afterProcess(Object item, @Nullable Object result) { if (result == null) { this.messagePublisher.publish(this.properties.getItemProcessEventBindingName(), "1 item was filtered"); } @@ -88,8 +89,9 @@ public void onProcessError(Object item, Exception e) { if (logger.isDebugEnabled()) { logger.debug("Executing onProcessError: " + e.getMessage(), e); } + String message = e.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getItemProcessEventBindingName(), - "Exception while item was being processed", e.getMessage()); + "Exception while item was being processed", message != null ? message : e.getClass().getName()); } @Override diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemReadListener.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemReadListener.java index 37dd3adb9..7dc23d7c0 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemReadListener.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemReadListener.java @@ -75,9 +75,9 @@ public void onReadError(Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Executing onReadError: " + ex.getMessage(), ex); } - + String message = ex.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getItemReadEventBindingName(), - "Exception while item was being read", ex.getMessage()); + "Exception while item was being read", message != null ? message : ex.getClass().getName()); } @Override diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemWriteListener.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemWriteListener.java index 2f3db8726..15ee14605 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemWriteListener.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingItemWriteListener.java @@ -83,8 +83,9 @@ public void onWriteError(Exception exception, Chunk items) { logger.debug("Executing onWriteError: " + exception.getMessage(), exception); } String payload = "Exception while " + items.size() + " items are attempted to be written."; + String message = exception.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getItemWriteEventBindingName(), payload, - exception.getMessage()); + message != null ? message : exception.getClass().getName()); } @Override diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingSkipListener.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingSkipListener.java index 22269dc4c..fdb1fc410 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingSkipListener.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/EventEmittingSkipListener.java @@ -67,8 +67,9 @@ public void onSkipInRead(Throwable t) { if (logger.isDebugEnabled()) { logger.debug("Executing onSkipInRead: " + t.getMessage(), t); } + String message = t.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getSkipEventBindingName(), - "Skipped when reading.", t.getMessage()); + "Skipped when reading.", message != null ? message : t.getClass().getName()); } @Override @@ -76,8 +77,9 @@ public void onSkipInWrite(Object item, Throwable t) { if (logger.isDebugEnabled()) { logger.debug("Executing onSkipInWrite: " + t.getMessage(), t); } + String message = t.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getSkipEventBindingName(), item, - t.getMessage()); + message != null ? message : t.getClass().getName()); } @Override @@ -85,8 +87,9 @@ public void onSkipInProcess(Object item, Throwable t) { if (logger.isDebugEnabled()) { logger.debug("Executing onSkipInProcess: " + t.getMessage(), t); } + String message = t.getMessage(); this.messagePublisher.publishWithThrowableHeader(this.properties.getSkipEventBindingName(), item, - t.getMessage()); + message != null ? message : t.getClass().getName()); } @Override diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java index 15cf1adbb..fae184593 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/package-info.java @@ -17,4 +17,7 @@ /** * Stream-based batch listener components for Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.batch.listener; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/ExitStatus.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/ExitStatus.java index afe27c4c9..e28d66204 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/ExitStatus.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/ExitStatus.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.listener.support; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -26,9 +28,9 @@ */ public class ExitStatus { - private String exitCode; + private @Nullable String exitCode; - private String exitDescription; + private @Nullable String exitDescription; public ExitStatus() { } @@ -40,7 +42,7 @@ public ExitStatus(org.springframework.batch.core.ExitStatus exitStatus) { this.exitDescription = exitStatus.getExitDescription(); } - public String getExitCode() { + public @Nullable String getExitCode() { return this.exitCode; } @@ -48,7 +50,7 @@ public void setExitCode(String exitCode) { this.exitCode = exitCode; } - public String getExitDescription() { + public @Nullable String getExitDescription() { return this.exitDescription; } diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobExecutionEvent.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobExecutionEvent.java index 1893bbbd3..ae903a45f 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobExecutionEvent.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobExecutionEvent.java @@ -25,6 +25,8 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Entity; import org.springframework.batch.core.job.JobExecution; @@ -39,21 +41,21 @@ */ public class JobExecutionEvent extends Entity { - private JobParametersEvent jobParameters; + private @Nullable JobParametersEvent jobParameters; - private JobInstanceEvent jobInstance; + private @Nullable JobInstanceEvent jobInstance; private Collection stepExecutions = Collections.synchronizedList(new ArrayList<>()); private BatchStatus status = BatchStatus.STARTING; - private LocalDateTime startTime = null; + private @Nullable LocalDateTime startTime = null; private LocalDateTime createTime = LocalDateTime.now(); - private LocalDateTime endTime = null; + private @Nullable LocalDateTime endTime = null; - private LocalDateTime lastUpdated = null; + private @Nullable LocalDateTime lastUpdated = null; private ExitStatus exitStatus = new ExitStatus(new org.springframework.batch.core.ExitStatus("UNKNOWN")); @@ -85,26 +87,29 @@ public JobExecutionEvent(JobExecution original) { this.exitStatus = new ExitStatus(original.getExitStatus()); this.executionContext = original.getExecutionContext(); this.failureExceptions = original.getFailureExceptions(); - this.setVersion(original.getVersion()); + Integer version = original.getVersion(); + if (version != null) { + this.setVersion(version); + } } - public JobParametersEvent getJobParameters() { + public @Nullable JobParametersEvent getJobParameters() { return this.jobParameters; } - public LocalDateTime getEndTime() { + public @Nullable LocalDateTime getEndTime() { return this.endTime; } - public void setEndTime(LocalDateTime endTime) { + public void setEndTime(@Nullable LocalDateTime endTime) { this.endTime = endTime; } - public LocalDateTime getStartTime() { + public @Nullable LocalDateTime getStartTime() { return this.startTime; } - public void setStartTime(LocalDateTime startTime) { + public void setStartTime(@Nullable LocalDateTime startTime) { this.startTime = startTime; } @@ -134,7 +139,7 @@ public void upgradeStatus(BatchStatus status) { * Convenience getter for the id of the enclosing job. Useful for DAO implementations. * @return the id of the enclosing job */ - public Long getJobId() { + public @Nullable Long getJobId() { if (this.jobInstance != null) { return this.jobInstance.getId(); } @@ -158,11 +163,11 @@ public void setExitStatus(ExitStatus exitStatus) { /** * @return the Job that is executing. */ - public JobInstanceEvent getJobInstance() { + public @Nullable JobInstanceEvent getJobInstance() { return this.jobInstance; } - public void setJobInstance(JobInstanceEvent jobInstance) { + public void setJobInstance(@Nullable JobInstanceEvent jobInstance) { this.jobInstance = jobInstance; } @@ -210,7 +215,7 @@ public void setCreateTime(LocalDateTime createTime) { * JobRepository. * @return Date representing the last time this JobExecution was updated. */ - public LocalDateTime getLastUpdated() { + public @Nullable LocalDateTime getLastUpdated() { return this.lastUpdated; } @@ -218,7 +223,7 @@ public LocalDateTime getLastUpdated() { * Set the last time this {@link JobExecution} was updated. * @param lastUpdated The date the {@link JobExecution} was updated. */ - public void setLastUpdated(LocalDateTime lastUpdated) { + public void setLastUpdated(@Nullable LocalDateTime lastUpdated) { this.lastUpdated = lastUpdated; } diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobInstanceEvent.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobInstanceEvent.java index df98c41f3..f82c1edad 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobInstanceEvent.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobInstanceEvent.java @@ -16,6 +16,8 @@ package org.springframework.cloud.task.batch.listener.support; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.core.Entity; import org.springframework.util.Assert; @@ -29,7 +31,7 @@ public class JobInstanceEvent extends Entity { - private String jobName; + private @Nullable String jobName; public JobInstanceEvent() { super(-1L); @@ -44,7 +46,7 @@ public JobInstanceEvent(Long id, String jobName) { /** * @return the job name. (Equivalent to getJob().getName()) */ - public String getJobName() { + public @Nullable String getJobName() { return this.jobName; } diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobParameterEvent.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobParameterEvent.java index 61a1baf2d..6ab555319 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobParameterEvent.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/JobParameterEvent.java @@ -19,6 +19,8 @@ import java.util.Date; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.core.job.parameters.JobParameter; /** @@ -30,7 +32,7 @@ */ public class JobParameterEvent { - private Object parameter; + private @Nullable Object parameter; private boolean identifying; @@ -49,10 +51,10 @@ public boolean isIdentifying() { /** * @return the value contained within this JobParameter. */ - public Object getValue() { + public @Nullable Object getValue() { - if (this.parameter != null && this.parameter.getClass().isInstance(Date.class)) { - return new Date(((Date) this.parameter).getTime()); + if (this.parameter instanceof Date dateParameter) { + return new Date((dateParameter).getTime()); } else { return this.parameter; @@ -74,15 +76,15 @@ public boolean equals(Object obj) { } @Override - public String toString() { - return this.parameter == null ? null : this.parameter.toString(); + public @Nullable String toString() { + return this.parameter == null ? "" : this.parameter.toString(); } @Override public int hashCode() { final int BASE_HASH = 7; final int MULTIPLIER_HASH = 21; - return BASE_HASH + MULTIPLIER_HASH * this.parameter.hashCode(); + return BASE_HASH + MULTIPLIER_HASH * (this.parameter != null ? this.parameter.hashCode() : 0); } } diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/StepExecutionEvent.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/StepExecutionEvent.java index 9a131641d..f9bc14242 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/StepExecutionEvent.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/StepExecutionEvent.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.jspecify.annotations.Nullable; + import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Entity; import org.springframework.batch.core.step.StepExecution; @@ -37,7 +39,7 @@ public class StepExecutionEvent extends Entity { private long jobExecutionId; - private String stepName; + private @Nullable String stepName; private BatchStatus status = BatchStatus.STARTING; @@ -55,11 +57,11 @@ public class StepExecutionEvent extends Entity { private long writeSkipCount = 0; - private LocalDateTime startTime = LocalDateTime.now(); + private @Nullable LocalDateTime startTime = LocalDateTime.now(); - private LocalDateTime endTime = null; + private @Nullable LocalDateTime endTime = null; - private LocalDateTime lastUpdated = null; + private @Nullable LocalDateTime lastUpdated = null; private ExecutionContext executionContext = new ExecutionContext(); @@ -145,7 +147,7 @@ public void setCommitCount(int commitCount) { * Returns the time that this execution ended. * @return the time that this execution ended */ - public LocalDateTime getEndTime() { + public @Nullable LocalDateTime getEndTime() { return this.endTime; } @@ -153,7 +155,7 @@ public LocalDateTime getEndTime() { * Sets the time that this execution ended. * @param endTime the time that this execution ended */ - public void setEndTime(LocalDateTime endTime) { + public void setEndTime(@Nullable LocalDateTime endTime) { this.endTime = endTime; } @@ -225,7 +227,7 @@ public void setFilterCount(int filterCount) { * Gets the time this execution started. * @return the time this execution started */ - public LocalDateTime getStartTime() { + public @Nullable LocalDateTime getStartTime() { return this.startTime; } @@ -233,7 +235,7 @@ public LocalDateTime getStartTime() { * Sets the time this execution started. * @param startTime the time this execution started */ - public void setStartTime(LocalDateTime startTime) { + public void setStartTime(@Nullable LocalDateTime startTime) { this.startTime = startTime; } @@ -256,11 +258,11 @@ public void setStatus(BatchStatus status) { /** * @return the name of the step. */ - public String getStepName() { + public @Nullable String getStepName() { return this.stepName; } - public void setStepName(String stepName) { + public void setStepName(@Nullable String stepName) { this.stepName = stepName; } @@ -355,7 +357,7 @@ public void setProcessSkipCount(int processSkipCount) { /** * @return the Date representing the last time this execution was persisted. */ - public LocalDateTime getLastUpdated() { + public @Nullable LocalDateTime getLastUpdated() { return this.lastUpdated; } @@ -363,7 +365,7 @@ public LocalDateTime getLastUpdated() { * Set the time when the StepExecution was last updated before persisting. * @param lastUpdated the {@link LocalDateTime} the StepExecution was last updated. */ - public void setLastUpdated(LocalDateTime lastUpdated) { + public void setLastUpdated(@Nullable LocalDateTime lastUpdated) { this.lastUpdated = lastUpdated; } @@ -389,8 +391,8 @@ public boolean equals(Object obj) { } StepExecution other = (StepExecution) obj; - return this.stepName.equals(other.getStepName()) && (this.jobExecutionId == other.getJobExecutionId()) - && getId() == other.getId(); + return (this.stepName != null && this.stepName.equals(other.getStepName())) + && (this.jobExecutionId == other.getJobExecutionId()) && getId() == other.getId(); } /* diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/TaskBatchEventListenerBeanPostProcessor.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/TaskBatchEventListenerBeanPostProcessor.java index e98b6697a..cd9de74ba 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/TaskBatchEventListenerBeanPostProcessor.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/TaskBatchEventListenerBeanPostProcessor.java @@ -18,6 +18,8 @@ import java.lang.reflect.Field; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -42,6 +44,7 @@ import org.springframework.cloud.task.batch.listener.support.TaskBatchEventListenerBeanPostProcessor.RuntimeHint; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** @@ -88,13 +91,17 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro if (tasklet instanceof ChunkOrientedTasklet) { Field chunkProviderField = ReflectionUtils.findField(ChunkOrientedTasklet.class, "chunkProvider"); + Assert.state(chunkProviderField != null, "chunkProvider field must exist"); ReflectionUtils.makeAccessible(chunkProviderField); SimpleChunkProvider chunkProvider = (SimpleChunkProvider) ReflectionUtils .getField(chunkProviderField, tasklet); Field chunkProcessorField = ReflectionUtils.findField(ChunkOrientedTasklet.class, "chunkProcessor"); + Assert.state(chunkProcessorField != null, "chunkProcessor field must exist"); ReflectionUtils.makeAccessible(chunkProcessorField); SimpleChunkProcessor chunkProcessor = (SimpleChunkProcessor) ReflectionUtils .getField(chunkProcessorField, tasklet); + Assert.state(chunkProvider != null, "chunkProvider must not be null"); + Assert.state(chunkProcessor != null, "chunkProcessor must not be null"); registerItemReadEvents(chunkProvider); registerSkipEvents(chunkProvider); registerItemProcessEvents(chunkProcessor); @@ -176,7 +183,7 @@ private void registerStepExecutionEventListener(Object bean) { static class RuntimeHint implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection().registerType(ChunkOrientedTasklet.class, MemberCategory.DECLARED_FIELDS); } diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java index befb88e43..1b947e12a 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/batch/listener/support/package-info.java @@ -17,4 +17,7 @@ /** * Support classes for stream-based batch listener components in Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.batch.listener.support; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/listener/package-info.java b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/listener/package-info.java index d6ee2ece4..1f4407970 100644 --- a/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/listener/package-info.java +++ b/spring-cloud-task-stream/src/main/java/org/springframework/cloud/task/listener/package-info.java @@ -17,4 +17,7 @@ /** * Stream-based task lifecycle listener support for Spring Cloud Task. */ +@NullMarked package org.springframework.cloud.task.listener; + +import org.jspecify.annotations.NullMarked;