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 e998def1..bdd50016 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-2018 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.
@@ -27,6 +27,7 @@ import org.springframework.data.domain.Pageable;
*
* @author Glenn Renfro
* @author Michael Minella
+ * @author Gunnar Hillert
*/
public interface TaskExplorer {
@@ -105,4 +106,34 @@ public interface TaskExplorer {
* @return a Set of the ids of the job executions executed within the task.
*/
Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId);
+
+ /**
+ * Returns a {@link List} of the latest {@link TaskExecution} for 1 or more task names.
+ *
+ * Latest is defined by the most recent start time. A {@link TaskExecution} does not have to be finished
+ * (The results may including pending {@link TaskExecution}s).
+ *
+ * It is theoretically possible that a {@link TaskExecution} with the same name to have more than 1
+ * {@link TaskExecution} for the exact same start time. In that case the {@link TaskExecution} with the
+ * highest Task Execution ID is returned.
+ *
+ * This method will not consider end times in its calculations. Thus, when a task execution {@code A} starts
+ * after task execution {@code B} but finishes BEFORE task execution {@code A}, then task execution {@code B}
+ * is being returned.
+ *
+ * @param taskNames At least 1 task name must be provided
+ * @return List of TaskExecutions. May be empty but never null.
+ */
+ List getLatestTaskExecutionsByTaskNames(String... taskNames);
+
+
+ /**
+ * Returns the latest task execution for a given task name. Will ultimately apply the same algorithm underneath
+ * as {@link #getLatestTaskExecutionsByTaskNames(String...)} but will only return a single result.
+ *
+ * @param taskName Must not be null or empty
+ * @return The latest Task Execution or null
+ * @see #getLatestTaskExecutionsByTaskNames(String...)
+ */
+ TaskExecution getLatestTaskExecutionForTaskName(String taskName);
}
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 1f2dbbc5..9c1b14ba 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
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -40,11 +41,11 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
-import org.springframework.jdbc.core.JdbcOperations;
-import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -67,61 +68,75 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
public static final String FROM_CLAUSE = "%PREFIX%EXECUTION";
public static final String RUNNING_TASK_WHERE_CLAUSE =
- "where TASK_NAME = ? AND END_TIME IS NULL ";
+ "where TASK_NAME = :taskName AND END_TIME IS NULL ";
- public static final String TASK_NAME_WHERE_CLAUSE = "where TASK_NAME = ? ";
+ public static final String TASK_NAME_WHERE_CLAUSE = "where TASK_NAME = :taskName ";
private static final String SAVE_TASK_EXECUTION = "INSERT into %PREFIX%EXECUTION"
+ "(TASK_EXECUTION_ID, START_TIME, TASK_NAME, LAST_UPDATED, EXTERNAL_EXECUTION_ID, PARENT_EXECUTION_ID)"
- + "values (?, ?, ?, ?, ?, ?)";
+ + "values (:taskExecutionId, :startTime, :taskName, :lastUpdated, :externalExecutionId, :parentExecutionId)";
private static final String CREATE_TASK_ARGUMENT = "INSERT into "
- + "%PREFIX%EXECUTION_PARAMS(TASK_EXECUTION_ID, TASK_PARAM ) values (?, ?)";
+ + "%PREFIX%EXECUTION_PARAMS(TASK_EXECUTION_ID, TASK_PARAM ) values (:taskExecutionId, :taskParam)";
private static final String START_TASK_EXECUTION_PREFIX = "UPDATE %PREFIX%EXECUTION set "
- + "START_TIME = ?, TASK_NAME = ?, LAST_UPDATED = ?";
+ + "START_TIME = :startTime, TASK_NAME = :taskName, LAST_UPDATED = :lastUpdated";
private static final String START_TASK_EXECUTION_EXTERNAL_ID_SUFFIX = ", "
- + "EXTERNAL_EXECUTION_ID = ?, PARENT_EXECUTION_ID = ? where TASK_EXECUTION_ID = ?";
+ + "EXTERNAL_EXECUTION_ID = :externalExecutionId, PARENT_EXECUTION_ID = :parentExecutionId where TASK_EXECUTION_ID = :taskExecutionId";
- private static final String START_TASK_EXECUTION_SUFFIX = ", PARENT_EXECUTION_ID = ? where TASK_EXECUTION_ID = ?";
+ private static final String START_TASK_EXECUTION_SUFFIX = ", PARENT_EXECUTION_ID = :parentExecutionId where TASK_EXECUTION_ID = :taskExecutionId";
private static final String CHECK_TASK_EXECUTION_EXISTS = "SELECT COUNT(*) FROM "
- + "%PREFIX%EXECUTION WHERE TASK_EXECUTION_ID = ?";
+ + "%PREFIX%EXECUTION WHERE TASK_EXECUTION_ID = :taskExecutionId";
private static final String UPDATE_TASK_EXECUTION = "UPDATE %PREFIX%EXECUTION set "
- + "END_TIME = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, ERROR_MESSAGE = ?, "
- + "LAST_UPDATED = ? where TASK_EXECUTION_ID = ?";
+ + "END_TIME = :endTime, EXIT_CODE = :exitCode, EXIT_MESSAGE = :exitMessage, ERROR_MESSAGE = :errorMessage, "
+ + "LAST_UPDATED = :lastUpdated where TASK_EXECUTION_ID = :taskExecutionId";
private static final String UPDATE_TASK_EXECUTION_EXTERNAL_EXECUTION_ID = "UPDATE %PREFIX%EXECUTION set "
- + "EXTERNAL_EXECUTION_ID = ? where TASK_EXECUTION_ID = ?";
+ + "EXTERNAL_EXECUTION_ID = :externalExecutionId where TASK_EXECUTION_ID = :taskExecutionId";
private static final String GET_EXECUTION_BY_ID = "SELECT TASK_EXECUTION_ID, " +
"START_TIME, END_TIME, TASK_NAME, EXIT_CODE, "
+ "EXIT_MESSAGE, ERROR_MESSAGE, LAST_UPDATED, EXTERNAL_EXECUTION_ID, "
+ "PARENT_EXECUTION_ID "
- + "from %PREFIX%EXECUTION where TASK_EXECUTION_ID = ?";
+ + "from %PREFIX%EXECUTION where TASK_EXECUTION_ID = :taskExecutionId";
private static final String FIND_ARGUMENT_FROM_ID = "SELECT TASK_EXECUTION_ID, "
- + "TASK_PARAM from %PREFIX%EXECUTION_PARAMS where TASK_EXECUTION_ID = ?";
+ + "TASK_PARAM from %PREFIX%EXECUTION_PARAMS where TASK_EXECUTION_ID = :taskExecutionId";
private static final String TASK_EXECUTION_COUNT = "SELECT COUNT(*) FROM " +
"%PREFIX%EXECUTION ";
private static final String TASK_EXECUTION_COUNT_BY_NAME = "SELECT COUNT(*) FROM " +
- "%PREFIX%EXECUTION where TASK_NAME = ?";
+ "%PREFIX%EXECUTION where TASK_NAME = :taskName";
private static final String RUNNING_TASK_EXECUTION_COUNT_BY_NAME = "SELECT COUNT(*) FROM " +
- "%PREFIX%EXECUTION where TASK_NAME = ? AND END_TIME IS NULL ";
+ "%PREFIX%EXECUTION where TASK_NAME = :taskName AND END_TIME IS NULL ";
+
+ private static final String LAST_TASK_EXECUTIONS_BY_TASK_NAMES =
+ "select TE2.* from (" +
+ "select MAX(TE.TASK_EXECUTION_ID) as TASK_EXECUTION_ID, TE.TASK_NAME, TE.START_TIME from (" +
+ "select TASK_NAME, MAX(START_TIME) as START_TIME" +
+ " FROM %PREFIX%EXECUTION where TASK_NAME in (:taskNames)" +
+ " GROUP BY TASK_NAME" +
+ ") TE_MAX " +
+ "inner join %PREFIX%EXECUTION TE ON TE.TASK_NAME = TE_MAX.TASK_NAME AND TE.START_TIME = TE_MAX.START_TIME " +
+ "group by TE.TASK_NAME, TE.START_TIME" +
+ ") TE1 " +
+ "inner join %PREFIX%EXECUTION TE2 ON TE1.TASK_EXECUTION_ID = TE2.TASK_EXECUTION_ID " +
+ "order by TE2.START_TIME DESC, TE2.TASK_EXECUTION_ID DESC";
private static final String FIND_TASK_NAMES = "SELECT distinct TASK_NAME from %PREFIX%EXECUTION order by TASK_NAME";
- private static final String FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID = "SELECT TASK_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE JOB_EXECUTION_ID = ?";
- private static final String FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID = "SELECT JOB_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE TASK_EXECUTION_ID = ?";
+ private static final String FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID = "SELECT TASK_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE JOB_EXECUTION_ID = :jobExecutionId";
+
+ private static final String FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID = "SELECT JOB_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE TASK_EXECUTION_ID = :taskExecutionId";
private String tablePrefix = TaskProperties.DEFAULT_TABLE_PREFIX;
- private JdbcOperations jdbcTemplate;
+ private final NamedParameterJdbcTemplate jdbcTemplate;
private DataSource dataSource;
@@ -146,8 +161,8 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
* @param dataSource used by the dao to execute queries and update the tables.
*/
public JdbcTaskExecutionDao(DataSource dataSource) {
- Assert.notNull(dataSource);
- this.jdbcTemplate = new JdbcTemplate(dataSource);
+ Assert.notNull(dataSource, "The dataSource must not be null.");
+ this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.dataSource = dataSource;
orderMap = new LinkedHashMap<>();
orderMap.put("START_TIME", Order.DESCENDING);
@@ -170,14 +185,17 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
TaskExecution taskExecution = new TaskExecution(nextExecutionId, null, taskName,
startTime, null, null, arguments, null, externalExecutionId);
- Object[] queryParameters = new Object[]{ nextExecutionId, startTime,
- taskName, new Date(), externalExecutionId,
- parentExecutionId};
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskExecutionId", nextExecutionId, Types.BIGINT)
+ .addValue("startTime", startTime, Types.TIMESTAMP)
+ .addValue("taskName", taskName, Types.VARCHAR)
+ .addValue("lastUpdated", new Date(), Types.TIMESTAMP)
+ .addValue("externalExecutionId", externalExecutionId, Types.VARCHAR)
+ .addValue("parentExecutionId", parentExecutionId, Types.BIGINT);
+
jdbcTemplate.update(
getQuery(SAVE_TASK_EXECUTION),
- queryParameters,
- new int[]{ Types.BIGINT, Types.TIMESTAMP, Types.VARCHAR,
- Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT});
+ queryParameters);
insertTaskArguments(nextExecutionId, arguments);
return taskExecution;
}
@@ -196,25 +214,25 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
String externalExecutionId, Long parentExecutionId) {
TaskExecution taskExecution = new TaskExecution(executionId, null, taskName,
startTime, null, null, arguments,null, externalExecutionId, parentExecutionId);
- Object[] queryParameters;
- int[] argTypes;
+
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("startTime", startTime, Types.TIMESTAMP)
+ .addValue("taskName", taskName, Types.VARCHAR)
+ .addValue("lastUpdated", new Date(), Types.TIMESTAMP)
+ .addValue("parentExecutionId", parentExecutionId, Types.BIGINT)
+ .addValue("taskExecutionId", executionId, Types.BIGINT);
+
String updateString = START_TASK_EXECUTION_PREFIX;
+
if(externalExecutionId == null) {
- queryParameters = new Object[]{startTime, taskName,
- new Date(), parentExecutionId, executionId};
updateString += START_TASK_EXECUTION_SUFFIX;
- argTypes = new int[]{Types.TIMESTAMP, Types.VARCHAR,
- Types.TIMESTAMP, Types.BIGINT, Types.BIGINT};
}
else {
- queryParameters = new Object[]{ startTime, taskName,
- new Date(), externalExecutionId, parentExecutionId, executionId};
- argTypes = new int[]{ Types.TIMESTAMP, Types.VARCHAR,
- Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT };
updateString += START_TASK_EXECUTION_EXTERNAL_ID_SUFFIX;
+ queryParameters.addValue("externalExecutionId", externalExecutionId, Types.VARCHAR);
}
- jdbcTemplate.update(getQuery(updateString), queryParameters, argTypes);
+ jdbcTemplate.update(getQuery(updateString), queryParameters);
insertTaskArguments(executionId, arguments);
return taskExecution;
}
@@ -222,20 +240,26 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public void completeTaskExecution(long taskExecutionId, Integer exitCode, Date endTime,
String exitMessage, 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 (jdbcTemplate.queryForObject(getQuery(CHECK_TASK_EXECUTION_EXISTS), Integer.class,
- taskExecutionId) != 1) {
+ if (jdbcTemplate.queryForObject(getQuery(CHECK_TASK_EXECUTION_EXISTS), queryParameters, Integer.class) != 1) {
throw new IllegalStateException("Invalid TaskExecution, ID " + taskExecutionId + " not found.");
}
- Object[] parameters = new Object[]{ endTime, exitCode, exitMessage, errorMessage, new Date(),
- taskExecutionId};
+ final MapSqlParameterSource parameters = new MapSqlParameterSource()
+ .addValue("endTime", endTime, Types.TIMESTAMP)
+ .addValue("exitCode", exitCode, Types.INTEGER)
+ .addValue("exitMessage", exitMessage, Types.VARCHAR)
+ .addValue("errorMessage", errorMessage, Types.VARCHAR)
+ .addValue("lastUpdated", new Date(), Types.TIMESTAMP)
+ .addValue("taskExecutionId", taskExecutionId, Types.BIGINT);
+
jdbcTemplate.update(
getQuery(UPDATE_TASK_EXECUTION),
- parameters,
- new int[]{ Types.TIMESTAMP, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP,
- Types.BIGINT});
+ parameters);
}
@Override
@@ -246,9 +270,12 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public TaskExecution getTaskExecution(long executionId) {
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskExecutionId", executionId, Types.BIGINT);
+
try {
TaskExecution taskExecution = jdbcTemplate.queryForObject(getQuery(GET_EXECUTION_BY_ID),
- new TaskExecutionRowMapper(), executionId);
+ queryParameters, new TaskExecutionRowMapper());
taskExecution.setArguments(getTaskArguments(executionId));
return taskExecution;
}
@@ -259,9 +286,13 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public long getTaskExecutionCountByTaskName(String taskName) {
+
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskName", taskName, Types.VARCHAR);
+
try {
- return jdbcTemplate.queryForObject(
- getQuery(TASK_EXECUTION_COUNT_BY_NAME), new Object[] { taskName }, Long.class);
+ return jdbcTemplate.queryForObject(
+ getQuery(TASK_EXECUTION_COUNT_BY_NAME), queryParameters, Long.class);
}
catch (EmptyResultDataAccessException e) {
return 0;
@@ -270,20 +301,65 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public long getRunningTaskExecutionCountByTaskName(String taskName) {
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskName", taskName, Types.VARCHAR);
+
try {
return jdbcTemplate.queryForObject(
- getQuery(RUNNING_TASK_EXECUTION_COUNT_BY_NAME), new Object[] { taskName }, Long.class);
+ getQuery(RUNNING_TASK_EXECUTION_COUNT_BY_NAME), queryParameters, Long.class);
}
catch (EmptyResultDataAccessException e) {
return 0;
}
}
+ @Override
+ public List getLatestTaskExecutionsByTaskNames(String... taskNames) {
+ Assert.notEmpty(taskNames, "At least 1 task name must be provided.");
+
+ final List taskNamesAsList = new ArrayList<>();
+
+ for (String taskName : taskNames) {
+ if (StringUtils.hasText(taskName)) {
+ taskNamesAsList.add(taskName);
+ }
+ }
+
+ Assert.isTrue(taskNamesAsList.size() == taskNames.length,
+ String.format("Task names must not contain any empty elements but %s of %s were empty or null.",
+ taskNames.length - taskNamesAsList.size(), taskNames.length));
+
+ try {
+ final Map> paramMap = Collections.singletonMap("taskNames", taskNamesAsList);
+ return this.jdbcTemplate.query(
+ getQuery(LAST_TASK_EXECUTIONS_BY_TASK_NAMES), paramMap, new TaskExecutionRowMapper());
+ }
+ catch (EmptyResultDataAccessException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public TaskExecution getLatestTaskExecutionForTaskName(String taskName) {
+ Assert.hasText(taskName, "The task name must not be empty.");
+ final List taskExecutions = this.getLatestTaskExecutionsByTaskNames(taskName);
+ if (taskExecutions.isEmpty()) {
+ return null;
+ }
+ else if (taskExecutions.size() == 1) {
+ return taskExecutions.get(0);
+ }
+ else {
+ throw new IllegalStateException("Only expected a single TaskExecution but received " + taskExecutions.size());
+ }
+ }
+
@Override
public long getTaskExecutionCount() {
+
try {
return jdbcTemplate.queryForObject(
- getQuery(TASK_EXECUTION_COUNT), new Object[] { }, Long.class);
+ getQuery(TASK_EXECUTION_COUNT), new MapSqlParameterSource(), Long.class);
}
catch (EmptyResultDataAccessException e) {
return 0;
@@ -293,26 +369,26 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public Page findRunningTaskExecutions(String taskName, Pageable pageable) {
return queryForPageableResults(pageable, SELECT_CLAUSE, FROM_CLAUSE,
- RUNNING_TASK_WHERE_CLAUSE, new Object[]{ taskName },
+ RUNNING_TASK_WHERE_CLAUSE, new MapSqlParameterSource("taskName", taskName),
getRunningTaskExecutionCountByTaskName(taskName));
}
@Override
public Page findTaskExecutionsByName(String taskName, Pageable pageable) {
return queryForPageableResults(pageable, SELECT_CLAUSE, FROM_CLAUSE,
- TASK_NAME_WHERE_CLAUSE, new Object[]{ taskName },
+ TASK_NAME_WHERE_CLAUSE, new MapSqlParameterSource("taskName", taskName),
getTaskExecutionCountByTaskName(taskName));
}
@Override
public List getTaskNames() {
- return jdbcTemplate.queryForList(getQuery(FIND_TASK_NAMES), String.class);
+ return jdbcTemplate.queryForList(getQuery(FIND_TASK_NAMES), new MapSqlParameterSource(), String.class);
}
@Override
public Page findAll(Pageable pageable) {
return queryForPageableResults(pageable, SELECT_CLAUSE, FROM_CLAUSE, null,
- new Object[]{ }, getTaskExecutionCount());
+ new MapSqlParameterSource(), getTaskExecutionCount());
}
public void setTaskIncrementer(DataFieldMaxValueIncrementer taskIncrementer) {
@@ -325,10 +401,13 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) {
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("jobExecutionId", jobExecutionId, Types.BIGINT);
+
try {
return jdbcTemplate.queryForObject(
getQuery(FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID),
- new Object[] { jobExecutionId },
+ queryParameters,
Long.class);
}
catch (EmptyResultDataAccessException e) {
@@ -338,10 +417,13 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId) {
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskExecutionId", taskExecutionId, Types.BIGINT);
+
try {
return jdbcTemplate.query(
getQuery(FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID),
- new Object[] {taskExecutionId},
+ queryParameters,
new ResultSetExtractor>() {
@Override
public Set extractData(ResultSet resultSet) throws SQLException, DataAccessException {
@@ -362,12 +444,13 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
@Override
public void updateExternalExecutionId(long taskExecutionId, String externalExecutionId) {
- Object[] parameters = new Object[]{externalExecutionId,
- taskExecutionId};
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("externalExecutionId", externalExecutionId, Types.VARCHAR)
+ .addValue("taskExecutionId", taskExecutionId, Types.BIGINT);
+
if (jdbcTemplate.update(
getQuery(UPDATE_TASK_EXECUTION_EXTERNAL_EXECUTION_ID),
- parameters,
- new int[]{Types.VARCHAR, Types.BIGINT}) != 1) {
+ queryParameters) != 1) {
throw new IllegalStateException("Invalid TaskExecution, ID "
+ taskExecutionId + " not found.");
}
@@ -377,7 +460,7 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
String selectClause,
String fromClause,
String whereClause,
- Object[] queryParam,
+ MapSqlParameterSource queryParameters,
long totalCount){
SqlPagingQueryProviderFactoryBean factoryBean = new SqlPagingQueryProviderFactoryBean();
factoryBean.setSelectClause(selectClause);
@@ -413,7 +496,7 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
String query = pagingQueryProvider.getPageQuery(pageable);
List resultList = jdbcTemplate.query(
getQuery(query),
- queryParam,
+ queryParameters,
new TaskExecutionRowMapper());
return new PageImpl<>(resultList, pageable, totalCount);
}
@@ -439,13 +522,14 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
* Convenience method that inserts an individual records into the
* TASK_EXECUTION_PARAMS table.
*/
- private void insertArgument(long executionId, String param) {
- int[] argTypes = new int[]{ Types.BIGINT, Types.VARCHAR };
- Object[] args = new Object[]{ executionId, param };
- jdbcTemplate.update(getQuery(CREATE_TASK_ARGUMENT), args, argTypes);
+ private void insertArgument(long taskExecutionId, String taskParam) {
+ final MapSqlParameterSource queryParameters = new MapSqlParameterSource()
+ .addValue("taskExecutionId", taskExecutionId, Types.BIGINT)
+ .addValue("taskParam", taskParam, Types.VARCHAR);
+ jdbcTemplate.update(getQuery(CREATE_TASK_ARGUMENT), queryParameters);
}
- private List getTaskArguments(long executionId){
+ private List getTaskArguments(long taskExecutionId){
final List params= new ArrayList<>();
RowCallbackHandler handler = new RowCallbackHandler() {
@Override
@@ -453,8 +537,7 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
params.add(rs.getString(2));
}
};
-
- jdbcTemplate.query(getQuery(FIND_ARGUMENT_FROM_ID), new Object[] { executionId },
+ jdbcTemplate.query(getQuery(FIND_ARGUMENT_FROM_ID), new MapSqlParameterSource("taskExecutionId", taskExecutionId),
handler);
return params;
}
@@ -486,10 +569,9 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao {
parentExecutionId);
}
- private Integer getNullableExitCode(ResultSet rs) throws SQLException {
- int exitCode = rs.getInt("EXIT_CODE");
- return !rs.wasNull() ? exitCode : null;
- }
+ private 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 4c67ceda..3ba30b25 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2017 the original author or authors.
+ * Copyright 2015-2018 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,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -33,11 +34,13 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
/**
* Stores Task Execution Information to a in-memory map.
*
* @author Glenn Renfro
+ * @author Gunnar Hillert
*/
public class MapTaskExecutionDao implements TaskExecutionDao {
@@ -251,4 +254,72 @@ public class MapTaskExecutionDao implements TaskExecutionDao {
executionList.subList((int)pageable.getOffset(), (int)toIndex),
pageable, maxSize);
}
+
+ @Override
+ public List getLatestTaskExecutionsByTaskNames(String... taskNames) {
+
+ Assert.notEmpty(taskNames, "At least 1 task name must be provided.");
+
+ final List taskNamesAsList = new ArrayList<>();
+
+ for (String taskName : taskNames) {
+ if (StringUtils.hasText(taskName)) {
+ taskNamesAsList.add(taskName);
+ }
+ }
+
+ Assert.isTrue(taskNamesAsList.size() == taskNames.length,
+ String.format("Task names must not contain any empty elements but %s of %s were empty or null.",
+ taskNames.length - taskNamesAsList.size(), taskNames.length));
+
+ final Map tempTaskExecutions = new HashMap<>();
+
+ for (Map.Entry taskExecutionMapEntry : this.taskExecutions.entrySet()) {
+ if (!taskNamesAsList.contains(taskExecutionMapEntry.getValue().getTaskName())) {
+ continue;
+ }
+
+ final TaskExecution tempTaskExecution = tempTaskExecutions.get(taskExecutionMapEntry.getValue().getTaskName());
+ if (tempTaskExecution == null
+ || tempTaskExecution.getStartTime().before(taskExecutionMapEntry.getValue().getStartTime())
+ || (
+ tempTaskExecution.getStartTime().equals(taskExecutionMapEntry.getValue().getStartTime())
+ && tempTaskExecution.getExecutionId() < taskExecutionMapEntry.getValue().getExecutionId()
+ )
+ ) {
+ tempTaskExecutions.put(taskExecutionMapEntry.getValue().getTaskName(), taskExecutionMapEntry.getValue());
+ }
+ }
+ final List latestTaskExecutions = new ArrayList<>(tempTaskExecutions.values());
+ Collections.sort(latestTaskExecutions, new TaskExecutionComparator());
+ return latestTaskExecutions;
+ }
+
+ @Override
+ public TaskExecution getLatestTaskExecutionForTaskName(String taskName) {
+ Assert.hasText(taskName, "The task name must not be empty.");
+ final List taskExecutions = this.getLatestTaskExecutionsByTaskNames(taskName);
+ if (taskExecutions.isEmpty()) {
+ return null;
+ }
+ else if (taskExecutions.size() == 1) {
+ return taskExecutions.get(0);
+ }
+ else {
+ throw new IllegalStateException("Only expected a single TaskExecution but received " + taskExecutions.size());
+ }
+ }
+
+ private class TaskExecutionComparator implements Comparator {
+ @Override
+ public int compare(TaskExecution firstTaskExecution, TaskExecution secondTaskExecution) {
+ if (firstTaskExecution.getStartTime().equals(secondTaskExecution.getStartTime())) {
+ return Long.compare(firstTaskExecution.getExecutionId(), secondTaskExecution.getExecutionId());
+ }
+ else {
+ return secondTaskExecution.getStartTime().compareTo(firstTaskExecution.getStartTime());
+ }
+ }
+ }
+
}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java
index 69f9626c..13d48909 100644
--- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2017 the original author or authors.
+ * Copyright 2015-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@ import org.springframework.data.domain.Pageable;
* Data Access Object for task executions.
*
* @author Glenn Renfro
+ * @author Gunnar Hillert
+ *
*/
public interface TaskExecutionDao {
@@ -201,4 +203,33 @@ public interface TaskExecutionDao {
*/
void updateExternalExecutionId(long taskExecutionId,
String externalExecutionId);
+
+ /**
+ * Returns a {@link List} of the latest {@link TaskExecution} for 1 or more task names.
+ *
+ * Latest is defined by the most recent start time. A {@link TaskExecution} does not have to be finished
+ * (The results may including pending {@link TaskExecution}s).
+ *
+ * It is theoretically possible that a {@link TaskExecution} with the same name to have more than 1
+ * {@link TaskExecution} for the exact same start time. In that case the {@link TaskExecution} with the
+ * highest Task Execution ID is returned.
+ *
+ * This method will not consider end times in its calculations. Thus, when a task execution {@code A} starts
+ * after task execution {@code B} but finishes BEFORE task execution {@code A}, then task execution {@code B}
+ * is being returned.
+ *
+ * @param taskNames At least 1 task name must be provided
+ * @return List of TaskExecutions. May be empty but never null.
+ */
+ List getLatestTaskExecutionsByTaskNames(String... taskNames);
+
+ /**
+ * Returns the latest task execution for a given task name. Will ultimately apply the same algorithm underneath
+ * as {@link #getLatestTaskExecutionsByTaskNames(String...)} but will only return a single result.
+ *
+ * @param taskName Must not be null or empty
+ * @return The latest Task Execution or null
+ * @see #getLatestTaskExecutionsByTaskNames(String...)
+ */
+ TaskExecution getLatestTaskExecutionForTaskName(String taskName);
}
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 49f70e7d..d955c06f 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-2018 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.
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
*
* @author Glenn Renfro
* @author Michael Minella
+ * @author Gunnar Hillert
*/
public class SimpleTaskExplorer implements TaskExplorer {
@@ -92,4 +93,14 @@ public class SimpleTaskExplorer implements TaskExplorer {
return taskExecutionDao.getJobExecutionIdsByTaskExecutionId(taskExecutionId);
}
+ @Override
+ public List getLatestTaskExecutionsByTaskNames(String... taskNames) {
+ return taskExecutionDao.getLatestTaskExecutionsByTaskNames(taskNames);
+ }
+
+ @Override
+ public TaskExecution getLatestTaskExecutionForTaskName(String taskName) {
+ return taskExecutionDao.getLatestTaskExecutionForTaskName(taskName);
+ }
+
}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/BaseTaskExecutionDaoTestCases.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/BaseTaskExecutionDaoTestCases.java
new file mode 100644
index 00000000..1c57e971
--- /dev/null
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/BaseTaskExecutionDaoTestCases.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://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.cloud.task.repository.dao;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.junit.Test;
+import org.springframework.cloud.task.repository.TaskExecution;
+import org.springframework.test.annotation.DirtiesContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Defines test cases that shall be shared between {@link JdbcTaskExecutionDaoTests} and {@link MapTaskExecutionDaoTests}.
+ *
+ * @author Gunnar Hillert
+ */
+public class BaseTaskExecutionDaoTestCases {
+
+ protected TaskExecutionDao dao;
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithNullParameter() {
+ try {
+ dao.getLatestTaskExecutionsByTaskNames(null);
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("At least 1 task name must be provided.", e.getMessage());
+ return;
+ }
+ fail("Expected an IllegalArgumentException to be thrown.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithEmptyArrayParameter() {
+ try {
+ dao.getLatestTaskExecutionsByTaskNames(new String[0]);
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("At least 1 task name must be provided.", e.getMessage());
+ return;
+ }
+ fail("Expected an IllegalArgumentException to be thrown.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithArrayParametersContainingNullAndEmptyValues() {
+ try {
+ dao.getLatestTaskExecutionsByTaskNames("foo", null, "bar", " ");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Task names must not contain any empty elements but 2 of 4 were empty or null.", e.getMessage());
+ return;
+ }
+ fail("Expected an IllegalArgumentException to be thrown.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithSingleTaskName() {
+ initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final List latestTaskExecutions = dao.getLatestTaskExecutionsByTaskNames("FOO1");
+ assertTrue("Expected only 1 taskExecution but got " + latestTaskExecutions.size(), latestTaskExecutions.size() == 1);
+
+ final TaskExecution lastTaskExecution = latestTaskExecutions.get(0);
+ assertEquals("FOO1", lastTaskExecution.getTaskName());
+
+ final Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTime.setTime(lastTaskExecution.getStartTime());
+
+ assertEquals(2015, dateTime.get(Calendar.YEAR));
+ assertEquals(2, dateTime.get(Calendar.MONTH) + 1);
+ assertEquals(22, dateTime.get(Calendar.DAY_OF_MONTH));
+ assertEquals(23, dateTime.get(Calendar.HOUR_OF_DAY));
+ assertEquals(59, dateTime.get(Calendar.MINUTE));
+ assertEquals(0, dateTime.get(Calendar.SECOND));
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithMultipleTaskNames() {
+ initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final List latestTaskExecutions = dao.getLatestTaskExecutionsByTaskNames("FOO1", "FOO3", "FOO4");
+ assertTrue("Expected 3 taskExecutions but got " + latestTaskExecutions.size(), latestTaskExecutions.size() == 3);
+
+ final Calendar dateTimeFoo3 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTimeFoo3.setTime(latestTaskExecutions.get(0).getStartTime());
+
+ assertEquals(2016, dateTimeFoo3.get(Calendar.YEAR));
+ assertEquals(8, dateTimeFoo3.get(Calendar.MONTH) + 1);
+ assertEquals(20, dateTimeFoo3.get(Calendar.DAY_OF_MONTH));
+ assertEquals(14, dateTimeFoo3.get(Calendar.HOUR_OF_DAY));
+ assertEquals(45, dateTimeFoo3.get(Calendar.MINUTE));
+ assertEquals(0, dateTimeFoo3.get(Calendar.SECOND));
+
+ final Calendar dateTimeFoo1 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTimeFoo1.setTime(latestTaskExecutions.get(1).getStartTime());
+
+ assertEquals(2015, dateTimeFoo1.get(Calendar.YEAR));
+ assertEquals(2, dateTimeFoo1.get(Calendar.MONTH) + 1);
+ assertEquals(22, dateTimeFoo1.get(Calendar.DAY_OF_MONTH));
+ assertEquals(23, dateTimeFoo1.get(Calendar.HOUR_OF_DAY));
+ assertEquals(59, dateTimeFoo1.get(Calendar.MINUTE));
+ assertEquals(0, dateTimeFoo1.get(Calendar.SECOND));
+
+ final Calendar dateTimeFoo4 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTimeFoo4.setTime(latestTaskExecutions.get(2).getStartTime());
+
+ assertEquals(2015, dateTimeFoo4.get(Calendar.YEAR));
+ assertEquals(2, dateTimeFoo4.get(Calendar.MONTH) + 1);
+ assertEquals(20, dateTimeFoo4.get(Calendar.DAY_OF_MONTH));
+ assertEquals(14, dateTimeFoo4.get(Calendar.HOUR_OF_DAY));
+ assertEquals(45, dateTimeFoo4.get(Calendar.MINUTE));
+ assertEquals(0, dateTimeFoo4.get(Calendar.SECOND));
+ }
+
+ /**
+ * This test is a special use-case. While not common, it is theoretically possible, that a task may have
+ * executed with the exact same start time multiple times. In that case we should still only get 1 returned
+ * {@link TaskExecution}.
+ */
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionsByTaskNamesWithIdenticalTaskExecutions() {
+ long executionIdOffset = initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final List latestTaskExecutions = dao.getLatestTaskExecutionsByTaskNames("FOO5");
+ assertTrue("Expected only 1 taskExecution but got " + latestTaskExecutions.size(), latestTaskExecutions.size() == 1);
+
+ final Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTime.setTime(latestTaskExecutions.get(0).getStartTime());
+
+ assertEquals(2015, dateTime.get(Calendar.YEAR));
+ assertEquals(2, dateTime.get(Calendar.MONTH) + 1);
+ assertEquals(22, dateTime.get(Calendar.DAY_OF_MONTH));
+ assertEquals(23, dateTime.get(Calendar.HOUR_OF_DAY));
+ assertEquals(59, dateTime.get(Calendar.MINUTE));
+ assertEquals(0, dateTime.get(Calendar.SECOND));
+ assertEquals(9 + executionIdOffset, latestTaskExecutions.get(0).getExecutionId());
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionForTaskNameWithNullParameter() {
+ try {
+ dao.getLatestTaskExecutionForTaskName(null);
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("The task name must not be empty.", e.getMessage());
+ return;
+ }
+ fail("Expected an IllegalArgumentException to be thrown.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionForTaskNameWithEmptyStringParameter() {
+ try {
+ dao.getLatestTaskExecutionForTaskName("");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("The task name must not be empty.", e.getMessage());
+ return;
+ }
+ fail("Expected an IllegalArgumentException to be thrown.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionForNonExistingTaskName() {
+ initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final TaskExecution latestTaskExecution = dao.getLatestTaskExecutionForTaskName("Bar5");
+ assertNull("Expected the latestTaskExecution to be null but got" + latestTaskExecution, latestTaskExecution);
+ }
+
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionForExistingTaskName() {
+ initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final TaskExecution latestTaskExecution = dao.getLatestTaskExecutionForTaskName("FOO1");
+ assertNotNull("Expected the latestTaskExecution not to be null", latestTaskExecution);
+
+ final Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTime.setTime(latestTaskExecution.getStartTime());
+
+ assertEquals(2015, dateTime.get(Calendar.YEAR));
+ assertEquals(2, dateTime.get(Calendar.MONTH) + 1);
+ assertEquals(22, dateTime.get(Calendar.DAY_OF_MONTH));
+ assertEquals(23, dateTime.get(Calendar.HOUR_OF_DAY));
+ assertEquals(59, dateTime.get(Calendar.MINUTE));
+ assertEquals(0, dateTime.get(Calendar.SECOND));
+ }
+
+ /**
+ * This test is a special use-case. While not common, it is theoretically possible, that a task may have
+ * executed with the exact same start time multiple times. In that case we should still only get 1 returned
+ * {@link TaskExecution}.
+ */
+ @Test
+ @DirtiesContext
+ public void getLatestTaskExecutionForTaskNameWithIdenticalTaskExecutions() {
+ long executionIdOffset = initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+ final TaskExecution latestTaskExecution = dao.getLatestTaskExecutionForTaskName("FOO5");
+ assertNotNull("Expected the latestTaskExecution not to be null", latestTaskExecution);
+
+ final Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ dateTime.setTime(latestTaskExecution.getStartTime());
+
+ assertEquals(2015, dateTime.get(Calendar.YEAR));
+ assertEquals(2, dateTime.get(Calendar.MONTH) + 1);
+ assertEquals(22, dateTime.get(Calendar.DAY_OF_MONTH));
+ assertEquals(23, dateTime.get(Calendar.HOUR_OF_DAY));
+ assertEquals(59, dateTime.get(Calendar.MINUTE));
+ assertEquals(0, dateTime.get(Calendar.SECOND));
+ assertEquals(9 + executionIdOffset, latestTaskExecution.getExecutionId());
+ }
+
+ protected long initializeRepositoryNotInOrderWithMultipleTaskExecutions() {
+
+ final TaskExecution foo1_0 = getTaskExecution("FOO1", "externalC");
+ foo1_0.setStartTime(getDate(2015, 2, 22, 23, 59));
+
+ final TaskExecution foo1_1 = getTaskExecution("FOO1", "externalC");
+ foo1_1.setStartTime(getDate(2015, 2, 20, 14, 45));
+
+ final TaskExecution foo1_2 = getTaskExecution("FOO1", "externalC");
+ foo1_2.setStartTime(getDate(2015, 1, 19, 14, 30));
+
+ final TaskExecution foo1_3 = getTaskExecution("FOO1", "externalC");
+ foo1_3.setStartTime(getDate(2015, 1, 20, 14, 45));
+
+ TaskExecution foo2 = getTaskExecution("FOO2", "externalA");
+ foo2.setStartTime(getDate(2015, 4, 20, 14, 45));
+
+ TaskExecution foo3 = getTaskExecution("FOO3", "externalB");
+ foo3.setStartTime(getDate(2016, 8, 20, 14, 45));
+
+ TaskExecution foo4 = getTaskExecution("FOO4", "externalB");
+ foo4.setStartTime(getDate(2015, 2, 20, 14, 45));
+
+ final TaskExecution foo5_0 = getTaskExecution("FOO5", "externalC");
+ foo5_0.setStartTime(getDate(2015, 2, 22, 23, 59));
+
+ final TaskExecution foo5_1 = getTaskExecution("FOO5", "externalC");
+ foo5_1.setStartTime(getDate(2015, 2, 22, 23, 59));
+
+ final TaskExecution foo5_2 = getTaskExecution("FOO5", "externalC");
+ foo5_2.setStartTime(getDate(2015, 2, 22, 23, 59));
+
+ long executionIdOffset = this.createTaskExecution(foo1_0);
+ this.createTaskExecution(foo1_1);
+ this.createTaskExecution(foo1_2);
+ this.createTaskExecution(foo1_3);
+
+ this.createTaskExecution(foo2);
+ this.createTaskExecution(foo3);
+ this.createTaskExecution(foo4);
+
+ this.createTaskExecution(foo5_0);
+ this.createTaskExecution(foo5_1);
+ this.createTaskExecution(foo5_2);
+
+ return executionIdOffset;
+ }
+
+ private Date getDate(int year, int month, int day, int hour, int minute) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.clear();
+ calendar.set(year, month - 1, day, hour, minute);
+ return calendar.getTime();
+ }
+
+ private long createTaskExecution(TaskExecution te) {
+ return dao.createTaskExecution(te.getTaskName(), te.getStartTime(), te.getArguments(), te.getExternalExecutionId()).getExecutionId();
+ }
+
+ protected TaskExecution getTaskExecution(String taskName,
+ String externalExecutionId) {
+ TaskExecution taskExecution = new TaskExecution();
+ taskExecution.setTaskName(taskName);
+ taskExecution.setExternalExecutionId(externalExecutionId);
+ taskExecution.setStartTime(new Date());
+ return taskExecution;
+ }
+}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java
index cdbe1c51..110231d9 100644
--- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2017 the original author or authors.
+ * Copyright 2015-2018 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,12 +21,12 @@ import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
+
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
@@ -49,25 +49,25 @@ import static org.junit.Assert.assertEquals;
* Executes unit tests on JdbcTaskExecutionDao.
*
* @author Glenn Renfro
+ * @author Gunnar Hillert
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class})
-public class JdbcTaskExecutionDaoTests {
+public class JdbcTaskExecutionDaoTests extends BaseTaskExecutionDaoTestCases {
@Autowired
private DataSource dataSource;
- private JdbcTaskExecutionDao dao;
-
@Autowired
TaskRepository repository;
@Before
public void setup(){
- dao = new JdbcTaskExecutionDao(dataSource);
+ final JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource);
dao.setTaskIncrementer(TestDBUtils.getIncrementer(dataSource));
+ super.dao = dao;
}
@Test
@@ -221,13 +221,4 @@ public class JdbcTaskExecutionDaoTests {
repository.createTaskExecution(getTaskExecution("FOO2", "externalA"));
repository.createTaskExecution(getTaskExecution("FOO3", "externalB"));
}
-
- private TaskExecution getTaskExecution(String taskName,
- String externalExecutionId) {
- TaskExecution taskExecution = new TaskExecution();
- taskExecution.setTaskName(taskName);
- taskExecution.setExternalExecutionId(externalExecutionId);
- taskExecution.setStartTime(new Date());
- return taskExecution;
- }
}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java
index e75c4117..f1c7a742 100644
--- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2017 the original author or authors.
+ * Copyright 2015-2018 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.
@@ -27,7 +27,6 @@ import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.util.TestVerifierUtils;
@@ -37,15 +36,18 @@ import static org.junit.Assert.assertNull;
/**
* Executes unit tests on MapTaskExecutionDaoTests.
+ *
* @author Glenn Renfro
+ * @author Gunnar Hillert
*/
-public class MapTaskExecutionDaoTests {
+public class MapTaskExecutionDaoTests extends BaseTaskExecutionDaoTestCases{
- private MapTaskExecutionDao dao;
+ private MapTaskExecutionDao mapTaskExecutionDao;
@Before
public void setUp() {
- this.dao = new MapTaskExecutionDao();
+ this.mapTaskExecutionDao = new MapTaskExecutionDao();
+ super.dao = this.mapTaskExecutionDao;
}
@Test
@@ -59,7 +61,7 @@ public class MapTaskExecutionDaoTests {
this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
expectedTaskExecution.getExternalExecutionId());
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
assertNotNull("taskExecutionMap must not be null", taskExecutionMap);
TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
taskExecutionMap.get(expectedTaskExecution.getExecutionId()));
@@ -70,7 +72,7 @@ public class MapTaskExecutionDaoTests {
TaskExecution expectedTaskExecution = dao.createTaskExecution(null, null,
new ArrayList(0), null);
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
taskExecutionMap.get(expectedTaskExecution.getExecutionId()));
}
@@ -89,7 +91,7 @@ public class MapTaskExecutionDaoTests {
expectedTaskExecution = this.dao.createTaskExecution(expectedTaskExecution.getTaskName(),
expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
expectedTaskExecution.getExternalExecutionId());
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
assertNotNull("taskExecutionMap must not be null", taskExecutionMap);
TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
taskExecutionMap.get(expectedTaskExecution.getExecutionId()));
@@ -104,7 +106,7 @@ public class MapTaskExecutionDaoTests {
this.dao.completeTaskExecution(expectedTaskExecution.getExecutionId(),
expectedTaskExecution.getExitCode(), expectedTaskExecution.getEndTime(),
expectedTaskExecution.getExitMessage());
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
assertNotNull("taskExecutionMap must not be null", taskExecutionMap);
TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
taskExecutionMap.get(expectedTaskExecution.getExecutionId()));
@@ -124,10 +126,10 @@ public class MapTaskExecutionDaoTests {
expectedTaskExecution.getExitCode(), expectedTaskExecution.getEndTime(),
expectedTaskExecution.getExitMessage());
}
- Set jobIds = new HashSet(2);
+ Set jobIds = new HashSet<>(2);
jobIds.add(123L);
jobIds.add(456L);
- this.dao.getBatchJobAssociations().put(
+ this.mapTaskExecutionDao.getBatchJobAssociations().put(
expectedTaskExecutionList.get(0).getExecutionId(), jobIds);
assertEquals(Long.valueOf(expectedTaskExecutionList.get(0).getExecutionId()),
@@ -141,7 +143,7 @@ public class MapTaskExecutionDaoTests {
public void testStartExecutionWithNullExternalExecutionIdExisting(){
TaskExecution expectedTaskExecution =
initializeTaskExecutionWithExternalExecutionId();
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
null);
@@ -153,7 +155,7 @@ public class MapTaskExecutionDaoTests {
public void testStartExecutionWithNullExternalExecutionIdNonExisting(){
TaskExecution expectedTaskExecution =
initializeTaskExecutionWithExternalExecutionId();
- Map taskExecutionMap = this.dao.getTaskExecutions();
+ Map taskExecutionMap = this.mapTaskExecutionDao.getTaskExecutions();
this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
"BAR");
@@ -167,4 +169,5 @@ public class MapTaskExecutionDaoTests {
expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
"FOO1");
}
+
}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java
index 16f60ecb..515ea3c5 100644
--- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-2018 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.
@@ -31,7 +31,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
@@ -59,6 +58,7 @@ import org.springframework.data.domain.Pageable;
/**
* @author Glenn Renfro
+ * @author Gunnar Hillert
*/
@RunWith(Parameterized.class)
public class SimpleTaskExplorerTests {
@@ -124,7 +124,7 @@ public class SimpleTaskExplorerTests {
@Test
public void taskExecutionNotFound() {
- Map< Long, TaskExecution> expectedResults = createSampleDataSet(5);
+ createSampleDataSet(5);
TaskExecution actualTaskExecution =
taskExplorer.getTaskExecution(-5);
@@ -147,7 +147,7 @@ public class SimpleTaskExplorerTests {
@Test
public void getTaskCount() {
- Map expectedResults = createSampleDataSet(33);
+ createSampleDataSet(33);
assertEquals(String.format(
"task count did not match expected result for test Type %s",
testType),
@@ -192,7 +192,6 @@ public class SimpleTaskExplorerTests {
public void findTasksByName() {
final int TEST_COUNT = 5;
final int COMPLETE_COUNT = 7;
- Random randomGenerator = new Random();
Map expectedResults = new HashMap<>();
//Store completed jobs
@@ -271,12 +270,50 @@ public class SimpleTaskExplorerTests {
assertEquals(0, taskExplorer.getJobExecutionIdsByTaskExecutionId(555555L).size());
}
+ @Test
+ public void getLatestTaskExecutionForTaskName() {
+ Map expectedResults = createSampleDataSet(5);
+ for (Map.Entry taskExecutionMapEntry: expectedResults.entrySet()) {
+ TaskExecution latestTaskExecution =
+ taskExplorer.getLatestTaskExecutionForTaskName(taskExecutionMapEntry.getValue().getTaskName());
+ assertNotNull(String.format(
+ "expected a taskExecution but got null for test type %s", testType),
+ latestTaskExecution);
+ TestVerifierUtils.verifyTaskExecution(
+ expectedResults.get(latestTaskExecution.getExecutionId()),
+ latestTaskExecution);
+ }
+ }
+
+ @Test
+ public void getLatestTaskExecutionsByTaskNames() {
+ Map expectedResults = createSampleDataSet(5);
+
+ final List taskNamesAsList = new ArrayList<>();
+
+ for (TaskExecution taskExecution : expectedResults.values()) {
+ taskNamesAsList.add(taskExecution.getTaskName());
+ }
+
+ final List latestTaskExecutions = taskExplorer.getLatestTaskExecutionsByTaskNames(
+ taskNamesAsList.toArray(new String[taskNamesAsList.size()]));
+
+ for (TaskExecution latestTaskExecution : latestTaskExecutions) {
+ assertNotNull(String.format(
+ "expected a taskExecution but got null for test type %s", testType),
+ latestTaskExecution);
+ TestVerifierUtils.verifyTaskExecution(
+ expectedResults.get(latestTaskExecution.getExecutionId()),
+ latestTaskExecution);
+ }
+ }
+
private void verifyPageResults(Pageable pageable, int totalNumberOfExecs) {
Map expectedResults = createSampleDataSet(totalNumberOfExecs);
List sortedExecIds = getSortedOfTaskExecIds(expectedResults);
Iterator expectedTaskExecutionIter = sortedExecIds.iterator();
//Verify pageable totals
- Page taskPage = taskExplorer.findAll(pageable);
+ Page taskPage = taskExplorer.findAll(pageable);
int pagesExpected = (int) Math.ceil(totalNumberOfExecs / ((double) pageable.getPageSize()));
assertEquals("actual page count return was not the expected total",
pagesExpected,
@@ -356,7 +393,7 @@ public class SimpleTaskExplorerTests {
private List getSortedOfTaskExecIds(Map taskExecutionMap){
List sortedExecIds = new ArrayList<>(taskExecutionMap.size());
- TreeSet sortedSet = getTreeSet();
+ TreeSet sortedSet = getTreeSet();
sortedSet.addAll(taskExecutionMap.values());
Iterator iterator = sortedSet.descendingIterator();
while(iterator.hasNext()){
@@ -365,7 +402,7 @@ public class SimpleTaskExplorerTests {
return sortedExecIds;
}
- private TreeSet getTreeSet(){
+ private TreeSet getTreeSet(){
return new TreeSet(new Comparator() {
@Override
public int compare(TaskExecution e1, TaskExecution e2) {