Add default converters for java.util.Date parameter type

Resolves #4297
Resolves #4270
This commit is contained in:
Mahmoud Ben Hassine
2023-01-17 11:21:56 +01:00
parent 8aaf1d3d52
commit 7ecac1c2ff
13 changed files with 300 additions and 46 deletions

View File

@@ -26,6 +26,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.StringToDateConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
@@ -375,7 +377,10 @@ public class DefaultBatchConfiguration implements ApplicationContextAware {
* @return the {@link ConfigurableConversionService} to use.
*/
protected ConfigurableConversionService getConversionService() {
return new DefaultConversionService();
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new DateToStringConverter());
conversionService.addConverter(new StringToDateConverter());
return conversionService;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.converter;
import java.time.format.DateTimeFormatter;
/**
* Base class for {@link java.util.Date} converters.
*
* @author Mahmoud Ben Hassine
* @since 5.0.1
*/
public class AbstractDateConverter {
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_INSTANT;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.converter;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/**
* {@link Converter} implementation from {@link java.util.Date} to {@link String}.
*
* This converter formats dates according to the
* {@link java.time.format.DateTimeFormatter#ISO_INSTANT} format.
*
* @author Mahmoud Ben Hassine
* @since 5.0.1
*/
public class DateToStringConverter extends AbstractDateConverter implements Converter<Date, String> {
@Override
public String convert(Date source) {
return super.dateTimeFormatter.format(source.toInstant());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2022 the original author or authors.
* Copyright 2006-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,6 +53,14 @@ import org.springframework.util.StringUtils;
* service should be configured with a converter to and from string literals to job
* parameter types.
*
* By default, the Spring conversion service is augmented to support the conversion of the
* following types:
*
* <ul>
* <li>{@link java.util.Date}: in the
* {@link java.time.format.DateTimeFormatter#ISO_INSTANT} format</li>
* </ul>
*
* @author Dave Syer
* @author Michael Minella
* @author Mahmoud Ben Hassine
@@ -60,7 +68,14 @@ import org.springframework.util.StringUtils;
*/
public class DefaultJobParametersConverter implements JobParametersConverter {
protected ConfigurableConversionService conversionService = new DefaultConversionService();
protected ConfigurableConversionService conversionService;
public DefaultJobParametersConverter() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new DateToStringConverter());
conversionService.addConverter(new StringToDateConverter());
this.conversionService = conversionService;
}
/**
* @see org.springframework.batch.core.converter.JobParametersConverter#getJobParameters(java.util.Properties)

View File

@@ -47,6 +47,14 @@ import org.springframework.batch.core.JobParameters;
* service should be configured with a converter to and from string literals to job
* parameter types.
*
* By default, the Spring conversion service is augmented to support the conversion of the
* following types:
*
* <ul>
* <li>{@link java.util.Date}: in the
* {@link java.time.format.DateTimeFormatter#ISO_INSTANT} format</li>
* </ul>
*
* @author Mahmoud Ben Hassine
* @since 5.0
*

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.converter;
import java.time.Instant;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/**
* {@link Converter} implementation from {@link String} to {@link java.util.Date}.
*
* This converter expects strings in the
* {@link java.time.format.DateTimeFormatter#ISO_INSTANT} format.
*
* @author Mahmoud Ben Hassine
* @since 5.0.1
*/
public class StringToDateConverter extends AbstractDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
return Date.from(super.dateTimeFormatter.parse(source, Instant::from));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,8 @@ import java.nio.charset.StandardCharsets;
import javax.sql.DataSource;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.StringToDateConverter;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
@@ -164,7 +165,10 @@ public class JobExplorerFactoryBean extends AbstractJobExplorerFactoryBean imple
}
if (this.conversionService == null) {
this.conversionService = new DefaultConversionService();
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new DateToStringConverter());
conversionService.addConverter(new StringToDateConverter());
this.conversionService = conversionService;
}
super.afterPropertiesSet();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2022 the original author or authors.
* Copyright 2006-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -37,6 +36,8 @@ import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.StringToDateConverter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -106,7 +107,14 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements
private DataFieldMaxValueIncrementer jobExecutionIncrementer;
private ConfigurableConversionService conversionService = new DefaultConversionService();
private ConfigurableConversionService conversionService;
public JdbcJobExecutionDao() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new DateToStringConverter());
conversionService.addConverter(new StringToDateConverter());
this.conversionService = conversionService;
}
/**
* Public setter for the exit message length in database. Do not set this if you
@@ -127,7 +135,7 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements
}
/**
* Set the conversion service to use to convert job parameters from String literal to
* Set the conversion service to use to convert job parameters from String literals to
* typed values and vice versa.
*/
public void setConversionService(@NonNull ConfigurableConversionService conversionService) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,14 @@ import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Types;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.StringToDateConverter;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
@@ -235,7 +238,10 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i
}
if (this.conversionService == null) {
this.conversionService = new DefaultConversionService();
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new DateToStringConverter());
conversionService.addConverter(new StringToDateConverter());
this.conversionService = conversionService;
}
super.afterPropertiesSet();

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.converter;
import java.time.Instant;
import java.util.Date;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Test class for {@link DateToStringConverter}.
*
* @author Mahmoud Ben Hassine
*/
class DateToStringConverterTest {
private final DateToStringConverter converter = new DateToStringConverter();
@Test
void testConvert() {
// given
Date date = Date.from(Instant.EPOCH);
// when
String converted = this.converter.convert(date);
// then
Assertions.assertEquals("1970-01-01T00:00:00Z", converted);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2022 the original author or authors.
* Copyright 2006-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,33 +15,23 @@
*/
package org.springframework.batch.core.converter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.StringUtils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
* @author Michael Minella
@@ -55,7 +45,7 @@ class DefaultJobParametersConverterTests {
@Test
void testGetParametersIdentifyingWithIdentifyingKey() {
String jobKey = "job.key=myKey,java.lang.String,true";
String scheduleDate = "schedule.date=2008/01/23,java.util.Date,true";
String scheduleDate = "schedule.date=2008-01-23T10:15:30Z,java.util.Date,true";
String vendorId = "vendor.id=33243243,java.lang.Long,true";
String[] args = new String[] { jobKey, scheduleDate, vendorId };
@@ -70,7 +60,7 @@ class DefaultJobParametersConverterTests {
@Test
void testGetParametersIdentifyingByDefault() {
String jobKey = "job.key=myKey,java.lang.String";
String scheduleDate = "schedule.date=2008/01/23,java.util.Date";
String scheduleDate = "schedule.date=2008-01-23T10:15:30Z,java.util.Date";
String vendorId = "vendor.id=33243243,java.lang.Long";
String[] args = new String[] { jobKey, scheduleDate, vendorId };
@@ -85,7 +75,7 @@ class DefaultJobParametersConverterTests {
@Test
void testGetParametersNonIdentifying() {
String jobKey = "job.key=myKey,java.lang.String,false";
String scheduleDate = "schedule.date=2008/01/23,java.util.Date,false";
String scheduleDate = "schedule.date=2008-01-23T10:15:30Z,java.util.Date,false";
String vendorId = "vendor.id=33243243,java.lang.Long,false";
String[] args = new String[] { jobKey, scheduleDate, vendorId };
@@ -100,7 +90,7 @@ class DefaultJobParametersConverterTests {
@Test
void testGetParametersMixed() {
String jobKey = "job.key=myKey,java.lang.String,true";
String scheduleDate = "schedule.date=2008/01/23,java.util.Date";
String scheduleDate = "schedule.date=2008-01-23T10:15:30Z,java.util.Date";
String vendorId = "vendor.id=33243243,java.lang.Long,false";
String[] args = new String[] { jobKey, scheduleDate, vendorId };

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.converter;
import java.time.Instant;
import java.util.Date;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Test class for {@link StringToDateConverter}.
*
* @author Mahmoud Ben Hassine
*/
class StringToDateConverterTest {
private final StringToDateConverter converter = new StringToDateConverter();
@Test
void convert() {
// given
String date = "1970-01-01T00:00:00Z";
// when
Date converted = this.converter.convert(date);
// then
Assertions.assertEquals(Date.from(Instant.EPOCH), converted);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2022 the original author or authors.
* Copyright 2008-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,24 +17,19 @@ package org.springframework.batch.core.repository.dao;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.annotation.Transactional;
@@ -109,4 +104,30 @@ public class JdbcJobExecutionDaoTests extends AbstractJobExecutionDaoTests {
Assertions.assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "BATCH_JOB_EXECUTION_PARAMS"));
}
@Transactional
@Test
void testJobParametersPersistenceRoundTrip() {
// given
Date dateParameter = new Date();
String stringParameter = "foo";
long longParameter = 1L;
double doubleParameter = 2D;
JobParameters jobParameters = new JobParametersBuilder().addString("string", stringParameter)
.addLong("long", longParameter).addDouble("double", doubleParameter).addDate("date", dateParameter)
.toJobParameters();
JobExecution execution = new JobExecution(jobInstance, jobParameters);
// when
dao.saveJobExecution(execution);
execution = dao.getJobExecution(execution.getId());
// then
JobParameters parameters = execution.getJobParameters();
Assertions.assertNotNull(parameters);
Assertions.assertEquals(dateParameter, parameters.getDate("date"));
Assertions.assertEquals(stringParameter, parameters.getString("string"));
Assertions.assertEquals(longParameter, parameters.getLong("long"));
Assertions.assertEquals(doubleParameter, parameters.getDouble("double"));
}
}