From a9f882f24b536002a1b8f183630ee92ec4d39bf2 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Tue, 1 Dec 2015 17:45:57 -0500 Subject: [PATCH] SCT-6 Support RDBMS & Map Repositories * `DefaultTaskConfigurer` returns only one `SimpleTaskRepository` all others have been removed * `SimpleTaskRepository` can use `JdbcTaskExecutionDao` or a `MapTaskExecutionDao` * Add `Jdbc` and Map` `TaskRepositoryFactoryBeans` to support the creation of the correct type of `SimpleTaskRepository` * `TaskDatabaseInitializer` will initialize a database if a datasource is found and if they have not disabled it using the `spring.task.initialize.enable` * Add tests * Added Transaction Support * Re-add the samples to `pom.xml` * Samples pom skips install and deploy lifecycle targets. --- spring-cloud-task-core/pom.xml | 23 ++- .../cloud/task/annotation/EnableTask.java | 2 +- .../cloud/task/annotation/package-info.java | 4 + .../configuration/DefaultTaskConfigurer.java | 55 ++++++- .../SimpleTaskConfiguration.java | 52 ++++++- .../task/configuration/package-info.java | 4 + .../cloud/task/package-info.java | 4 + .../cloud/task/repository/TaskExecution.java | 41 +++-- .../cloud/task/repository/TaskRepository.java | 3 + .../repository/dao/JdbcTaskExecutionDao.java | 142 +++++++++++++++++ .../repository/dao/MapTaskExecutionDao.java | 52 +++++++ .../TaskExecutionDao.java} | 32 ++-- .../task/repository/dao/package-info.java | 5 + .../task/repository/support/DatabaseType.java | 104 +++++++++++++ .../JdbcTaskRepositoryFactoryBean.java | 79 ++++++++++ .../support/MapTaskRepositoryFactoryBean.java | 50 ++++++ .../support/SimpleTaskRepository.java | 73 +++++++++ .../support/TaskDatabaseInitializer.java | 76 +++++++++ .../task/repository/support/package-info.java | 4 + .../cloud/task/schema-hsqldb.sql | 18 +++ .../task/SimpleTaskConfigurationTests.java | 11 +- .../cloud/task/TaskHandlerDefaultTests.java | 17 +- .../dao/JdbcTaskExecutionDaoTests.java | 101 ++++++++++++ .../dao/MapTaskExecutionDaoTests.java | 57 +++++++ .../repository/support/DatabaseTypeTests.java | 88 +++++++++++ .../SimpleTaskRepositoryJdbcTests.java | 101 ++++++++++++ .../SimpleTaskRepositoryLoggerTests.java} | 44 +++--- .../support/SimpleTaskRepositoryMapTests.java | 83 ++++++++++ .../support/TaskDatabaseInitializerTests.java | 115 ++++++++++++++ .../TaskRepositoryFactoryBeanTests.java | 57 +++++++ .../cloud/task/util/LoggerTestUtils.java | 62 -------- .../cloud/task/util/TaskExecutionCreator.java | 74 +++++++++ .../cloud/task/util/TestDBUtils.java | 88 +++++++++++ .../task/util/TestDefaultConfiguration.java | 5 +- .../cloud/task/util/TestVerifierUtils.java | 145 ++++++++++++++++++ spring-cloud-task-samples/pom.xml | 20 +++ 36 files changed, 1738 insertions(+), 153 deletions(-) create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/package-info.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java rename spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/{support/LoggerTaskRepository.java => dao/TaskExecutionDao.java} (51%) create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/JdbcTaskRepositoryFactoryBean.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/MapTaskRepositoryFactoryBean.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializer.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java create mode 100644 spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryJdbcTests.java rename spring-cloud-task-core/src/test/java/org/springframework/cloud/task/{LoggerTaskRepositoryTests.java => repository/support/SimpleTaskRepositoryLoggerTests.java} (56%) create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskRepositoryFactoryBeanTests.java delete mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/LoggerTestUtils.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TaskExecutionCreator.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDBUtils.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml index dd1a6acb..efce4710 100755 --- a/spring-cloud-task-core/pom.xml +++ b/spring-cloud-task-core/pom.xml @@ -27,6 +27,10 @@ org.springframework.boot spring-boot + + org.springframework.boot + spring-boot-autoconfigure + org.springframework spring-context @@ -40,13 +44,12 @@ spring-aop - org.aspectj - aspectjweaver + org.springframework + spring-jdbc - junit - junit - test + org.aspectj + aspectjweaver org.springframework @@ -58,6 +61,16 @@ mockito-core test + + org.hsqldb + hsqldb + test + + + org.apache.tomcat + tomcat-jdbc + test + diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/EnableTask.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/EnableTask.java index ad871440..d1e266a1 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/EnableTask.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/EnableTask.java @@ -54,7 +54,7 @@ import org.springframework.context.annotation.Import; * You will also be able to @Autowired some useful stuff into your context: * * * * @author Glenn Renfro diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/package-info.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/package-info.java new file mode 100644 index 00000000..22b902ff --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * Annotations for spring cloud task. + */ +package org.springframework.cloud.task.annotation; 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 874eb1f5..d40b1c18 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 @@ -16,27 +16,68 @@ package org.springframework.cloud.task.configuration; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.cloud.task.repository.TaskRepository; -import org.springframework.cloud.task.repository.support.LoggerTaskRepository; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; +import org.springframework.cloud.task.repository.support.JdbcTaskRepositoryFactoryBean; +import org.springframework.cloud.task.repository.support.MapTaskRepositoryFactoryBean; +import org.springframework.cloud.task.repository.support.SimpleTaskRepository; /** - * If no {@link TaskConfigurer} is present, then this configuration will be used. + * Default implementation of the TaskConfigurer interface. If no {@link TaskConfigurer} + * implementation is present, then this configuration will be used. * The following defaults will be used: - * * * - * * @author Glenn Renfro */ -public class DefaultTaskConfigurer implements TaskConfigurer{ +public class DefaultTaskConfigurer implements TaskConfigurer { + + private final static Logger logger = LoggerFactory.getLogger(DefaultTaskConfigurer.class); + + private DataSource dataSource; + + private TaskRepository taskRepository; public DefaultTaskConfigurer(){ + initialize(); + } + + public DefaultTaskConfigurer(DataSource dataSource) { + this.dataSource = dataSource; + initialize(); } public TaskRepository getTaskRepository() { - return new LoggerTaskRepository(); + return taskRepository; + } + + public TaskExplorer getTaskExplorer() { + throw new UnsupportedOperationException("method not implemented"); + //TODO if datasource != null use TaskRepositoryFactoryBean from above like initialize method in DefaultBatchConfigurer + } + + private void initialize(){ + logger.debug("Initializing TaskRepository"); + if (dataSource == null) { + MapTaskRepositoryFactoryBean mapTaskRepositoryFactoryBean = + new MapTaskRepositoryFactoryBean(); + taskRepository = mapTaskRepositoryFactoryBean.getObject(); + } + else { + JdbcTaskRepositoryFactoryBean jdbcTaskRepositoryFactoryBean = + new JdbcTaskRepositoryFactoryBean(dataSource); + taskRepository = jdbcTaskRepositoryFactoryBean.getObject(); + } } } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java index f61d22d9..10c599d2 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java @@ -19,13 +19,20 @@ package org.springframework.cloud.task.configuration; import java.util.Collection; import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.support.TaskDatabaseInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; +import org.springframework.core.io.ResourceLoader; +import org.springframework.transaction.annotation.EnableTransactionManagement; /** * Base {@code Configuration} class providing common structure for enabling and using @@ -34,12 +41,25 @@ import org.springframework.context.annotation.Scope; * * @author Glenn Renfro */ +@EnableTransactionManagement @Configuration public class SimpleTaskConfiguration { + protected static final Log logger = LogFactory.getLog(SimpleTaskConfiguration.class); + + @Autowired private ApplicationContext context; + @Autowired(required = false) + private Collection dataSources; + + @Autowired + private ResourceLoader resourceLoader; + + @Value("${spring.class.initialize.enable:true}") + private boolean taskInitializationEnable; + private boolean initialized = false; private TaskRepository taskRepository; @@ -56,7 +76,7 @@ public class SimpleTaskConfiguration { public TaskRepository taskRepository(){ return taskRepository; } - + /** * Sets up the basic components by extracting them from the {@link TaskConfigurer}, defaulting to some * sensible values as long as a unique DataSource is available. @@ -66,6 +86,7 @@ public class SimpleTaskConfiguration { if (initialized) { return; } + logger.debug("Getting Task Configurer"); TaskConfigurer configurer = getConfigurer(context.getBeansOfType(TaskConfigurer.class).values()); taskRepository = configurer.getTaskRepository(); initialized = true; @@ -73,12 +94,32 @@ public class SimpleTaskConfiguration { private TaskConfigurer getConfigurer(Collection configurers) { if (this.configurer != null) { + logger.debug(String.format("Using %s TaskConfigurer", + configurer.getClass().getName())); return this.configurer; } if (configurers == null || configurers.isEmpty()) { - DefaultTaskConfigurer configurer = new DefaultTaskConfigurer(); - this.configurer = configurer; - return configurer; + if (dataSources == null || dataSources.isEmpty()) { + this.configurer = new DefaultTaskConfigurer(); + logger.debug(String.format("Using %s TaskConfigurer, with no datasource", + configurer.getClass().getName())); + return this.configurer; + } + else if (dataSources != null && dataSources.size() == 1) { + DataSource dataSource = dataSources.iterator().next(); + if(taskInitializationEnable) { + logger.debug("Initializing Task Schema"); + TaskDatabaseInitializer.initializeDatabase(dataSource, resourceLoader); + } + this.configurer = new DefaultTaskConfigurer(dataSource); + logger.debug(String.format("Using %s TaskConfigurer, with datasource", + configurer.getClass().getName())); + return this.configurer; + } + else { + throw new IllegalStateException("To use the default TaskConfigurer the context must contain no more than" + + "one DataSource, found " + dataSources.size()); + } } if (configurers.size() > 1) { throw new IllegalStateException( @@ -86,6 +127,9 @@ public class SimpleTaskConfiguration { + configurers.size()); } this.configurer = configurers.iterator().next(); + logger.debug(String.format("More than one Task Configurer available. Using" + + " first in list: %s TaskConfigurer", + configurer.getClass().getName())); return this.configurer; } } 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 new file mode 100644 index 00000000..8bbdd330 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/package-info.java @@ -0,0 +1,4 @@ +/** + * Interfaces for configuring Spring Cloud Task and a default implementations. + */ +package org.springframework.cloud.task.configuration; 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 new file mode 100644 index 00000000..235956ad --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/package-info.java @@ -0,0 +1,4 @@ +/** + * Base package for spring cloud task. + */ +package org.springframework.cloud.task; 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 a07adde5..4ccc6906 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 @@ -16,9 +16,12 @@ package org.springframework.cloud.task.repository; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.springframework.util.Assert; + /** * Represents the state of the Task for each execution. * @@ -26,23 +29,6 @@ import java.util.List; */ public class TaskExecution { - - public TaskExecution() { - } - - public TaskExecution(String executionId, int exitCode, String taskName, - Date startTime, Date endTime, String statusCode, - String exitMessage, List parameters) { - this.executionId = executionId; - this.exitCode = exitCode; - this.taskName = taskName; - this.startTime = startTime; - this.endTime = endTime; - this.statusCode = statusCode; - this.exitMessage = exitMessage; - this.parameters = parameters; - } - /** * The unique id associated with the task execution. */ @@ -83,6 +69,27 @@ public class TaskExecution { */ private List parameters; + public TaskExecution() { + parameters = new ArrayList<>(); + } + + public TaskExecution(String executionId, int exitCode, String taskName, + Date startTime, Date endTime, String statusCode, + String exitMessage, List parameters) { + + Assert.hasText(executionId, "executionId must not be null nor empty"); + Assert.notNull(parameters, "parameters must not be null"); + Assert.notNull(startTime, "startTime must not be null"); + this.executionId = executionId; + this.exitCode = exitCode; + this.taskName = taskName; + this.startTime = startTime; + this.endTime = endTime; + this.statusCode = statusCode; + this.exitMessage = exitMessage; + this.parameters = parameters; + } + public String getExecutionId() { return executionId; } 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 75737010..f727ac29 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 @@ -16,6 +16,8 @@ package org.springframework.cloud.task.repository; +import org.springframework.transaction.annotation.Transactional; + /** * TaskRepository interface offers methods that create and update task execution * information. @@ -36,5 +38,6 @@ public interface TaskRepository { * * @param taskExecution taskExecution to be recorded */ + @Transactional public void createTaskExecution(TaskExecution taskExecution); } 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 new file mode 100644 index 00000000..b55be4bf --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java @@ -0,0 +1,142 @@ +/* + * Copyright 2015 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.sql.Types; +import java.util.Date; +import java.util.List; + +import javax.sql.DataSource; + +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Stores Task Execution Information to a JDBC DataSource. + * + * @author Glenn Renfro + */ +public class JdbcTaskExecutionDao implements TaskExecutionDao { + + + private static final String SAVE_TASK_EXECUTION = "INSERT into %PREFIX%EXECUTION" + + "(TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, EXIT_CODE, " + + "EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE) values (?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String CREATE_TASK_PARAMETER = "INSERT into " + + "%PREFIX%EXECUTION_PARAMS(TASK_EXECUTION_ID, TASK_PARAM ) values (?, ?)"; + + private static final String CHECK_TASK_EXECUTION_EXISTS = "SELECT COUNT(*) FROM " + + "%PREFIX%EXECUTION WHERE TASK_EXECUTION_ID = ?"; + + private static final String UPDATE_TASK_EXECUTION = "UPDATE %PREFIX%EXECUTION set " + + "START_TIME = ?, END_TIME = ?, TASK_NAME = ?, EXIT_CODE = ?, " + + "EXIT_MESSAGE = ?, LAST_UPDATED = ?, STATUS_CODE = ? " + + "where TASK_EXECUTION_ID = ?"; + + + private static final String DEFAULT_TABLE_PREFIX = "TASK_"; + + private String tablePrefix = DEFAULT_TABLE_PREFIX; + + private JdbcOperations jdbcTemplate; + + public JdbcTaskExecutionDao(DataSource dataSource) { + Assert.notNull(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public void saveTaskExecution(TaskExecution taskExecution) { + Object[] parameters = new Object[]{ taskExecution.getExecutionId(), + taskExecution.getStartTime(), taskExecution.getEndTime(), + taskExecution.getTaskName(), taskExecution.getExitCode(), + taskExecution.getExitMessage(), new Date(), + taskExecution.getStatusCode() }; + int addCount = jdbcTemplate.update( + getQuery(SAVE_TASK_EXECUTION), + parameters, + new int[]{ Types.VARCHAR, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, + Types.INTEGER, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR }); + insertJobParameters(taskExecution.getExecutionId(), taskExecution.getParameters()); + } + + @Override + public void updateTaskExecution(TaskExecution taskExecution) { + // 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, + new Object[]{ taskExecution.getExecutionId() }) != 1) { + throw new IllegalStateException("Invalid TaskExecution, ID " + taskExecution.getExecutionId() + " not found."); + } + + Object[] parameters = new Object[]{ taskExecution.getStartTime(), taskExecution.getEndTime(), + taskExecution.getTaskName(), taskExecution.getExitCode(), + taskExecution.getExitMessage(), new Date(), taskExecution.getStatusCode(), + taskExecution.getExecutionId() }; + int count = jdbcTemplate.update( + getQuery(UPDATE_TASK_EXECUTION), + parameters, + new int[]{ Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, + Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR }); + } + + /** + * Public setter for the table prefix property. This will be prefixed to all + * the table names before queries are executed. Defaults to + * {@link #DEFAULT_TABLE_PREFIX}. + * + * @param tablePrefix the tablePrefix to set + */ + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + private String getQuery(String base) { + return StringUtils.replace(base, "%PREFIX%", tablePrefix); + } + + /** + * Convenience method that inserts all parameters from the provided + * task parameters. + * + * @param executionId The executionId to which the params are associated. + * @param taskParameters The parameters to be stored. + */ + private void insertJobParameters(String executionId, List taskParameters) { + for (String param : taskParameters) { + insertParameter(executionId, param); + } + } + + /** + * Convenience method that inserts an individual records into the + * TASK_EXECUTION_PARAMS table. + */ + private void insertParameter(String executionId, String param) { + + Object[] args = new Object[0]; + int[] argTypes = new int[]{ Types.VARCHAR, Types.VARCHAR }; + + args = new Object[]{ executionId, param }; + jdbcTemplate.update(getQuery(CREATE_TASK_PARAMETER), args, argTypes); + } + +} 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 new file mode 100644 index 00000000..5bb7d547 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 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.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * Stores Task Execution Information to a in-memory map. + * + * @author Glenn Renfro + */ +public class MapTaskExecutionDao implements TaskExecutionDao { + + private ConcurrentMap taskExecutions; + + public MapTaskExecutionDao() { + taskExecutions = new ConcurrentHashMap<>(); + } + + @Override + public void saveTaskExecution(TaskExecution taskExecution) { + taskExecutions.put(taskExecution.getExecutionId(), taskExecution); + } + + @Override + public void updateTaskExecution(TaskExecution taskExecution) { + taskExecutions.put(taskExecution.getExecutionId(), taskExecution); + } + + public Map getTaskExecutions(){ + return Collections.unmodifiableMap(taskExecutions); + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/LoggerTaskRepository.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java similarity index 51% rename from spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/LoggerTaskRepository.java rename to spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java index ef6ba1bc..b22c38f0 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/LoggerTaskRepository.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java @@ -14,30 +14,28 @@ * limitations under the License. */ -package org.springframework.cloud.task.repository.support; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package org.springframework.cloud.task.repository.dao; import org.springframework.cloud.task.repository.TaskExecution; -import org.springframework.cloud.task.repository.TaskRepository; /** - * {@link TaskRepository} implementation that will log the task execution information. + * Data Access Object for task executions. * * @author Glenn Renfro */ -public class LoggerTaskRepository implements TaskRepository { - private final static Logger logger = LoggerFactory.getLogger(LoggerTaskRepository.class); +public interface TaskExecutionDao { - @Override - public void update(TaskExecution taskExecution) { - logger.info("Updating: " + taskExecution.toString()); - } + /** + * Save a new {@link TaskExecution}. + * + * @param taskExecution the taskExecution to be stored. + */ + void saveTaskExecution(TaskExecution taskExecution); - @Override - public void createTaskExecution(TaskExecution taskExecution) { - logger.info("Creating: " + taskExecution.toString()); - } + /** + * Update and existing {@link TaskExecution}. + * + * @param taskExecution the taskExecution to be updated. + */ + void updateTaskExecution(TaskExecution taskExecution); } 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 new file mode 100644 index 00000000..e7bbc5ba --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/package-info.java @@ -0,0 +1,5 @@ +/** + * Interface DAO and default implementations for storing and retrieving data for tasks + * from a repository. + */ +package org.springframework.cloud.task.repository.dao; 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 new file mode 100644 index 00000000..d98d68f3 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 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.support; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.util.StringUtils; + + +/** + * Enum representing a database type, such as DB2 or oracle. The type also + * contains a product name, which is expected to be the same as the product name + * provided by the database driver's metadata. + * + * @author Glenn Renfro + */ +public enum DatabaseType { + + HSQL("HSQL Database Engine"), + ORACLE("Oracle"), + POSTGRES("PostgreSQL"); + + private static final Map dbNameMap; + + private final String productName; + + private DatabaseType(String productName) { + this.productName = productName; + } + + static{ + dbNameMap = new HashMap(); + for(DatabaseType type: values()){ + dbNameMap.put(type.getProductName(), type); + } + } + + /** + * Convenience method that pulls a database product name from the DataSource's metadata. + * + * @param dataSource the datasource used to extact metadata. + * @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 MetaDataAccessException { + String databaseProductName = + JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName").toString(); + if (StringUtils.hasText(databaseProductName) && !databaseProductName.equals("DB2/Linux") && databaseProductName.startsWith("DB2")) { + String databaseProductVersion = + JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductVersion").toString(); + if (!databaseProductVersion.startsWith("SQL")) { + databaseProductName = "DB2ZOS"; + } + else { + databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName); + } + } + else { + databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName); + } + return fromProductName(databaseProductName); + } + + /** + * Static method to obtain a DatabaseType from the provided product name. + * + * @param productName the name of the database. + * @return DatabaseType for given product name. + * @throws IllegalArgumentException if none is found. + */ + public static DatabaseType fromProductName(String productName){ + if(!dbNameMap.containsKey(productName)){ + throw new IllegalArgumentException("DatabaseType not found for product name: [" + + productName + "]"); + } + else{ + return dbNameMap.get(productName); + } + } + + private String getProductName() { + return productName; + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/JdbcTaskRepositoryFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/JdbcTaskRepositoryFactoryBean.java new file mode 100644 index 00000000..284fe20d --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/JdbcTaskRepositoryFactoryBean.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 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.support; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.cloud.task.repository.dao.TaskExecutionDao; + +/** + * Automates the creation of a {@link SimpleTaskRepository} which will persist task + * execution data into a database. Requires the user to describe what kind of database + * they are using. + * + * @author Glenn Renfro + */ +public class JdbcTaskRepositoryFactoryBean { + + public static final String DEFAULT_TABLE_PREFIX = "TASK_"; + + private static final Log logger = LogFactory.getLog(JdbcTaskRepositoryFactoryBean.class); + + private DataSource dataSource; + + private String tablePrefix = DEFAULT_TABLE_PREFIX; + + public JdbcTaskRepositoryFactoryBean(){ + + } + + public JdbcTaskRepositoryFactoryBean(DataSource dataSource) { + if(dataSource != null) { + this.dataSource = dataSource; + } + } + + /** + * Sets the table prefix for all the batch meta-data tables. + * @param tablePrefix prefix prepended to batch meta-data tables + */ + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + /** + * Returns the a simpleTaskRepository that utilizes a MapTaskExecutionDao + * @return instance of task repository. + */ + public TaskRepository getObject(){ + TaskRepository taskRepository = null; + logger.debug(String.format("Creating SimpleTaskRepository that will use a %s", + JdbcTaskExecutionDao.class.getName())); + taskRepository = new SimpleTaskRepository(createJdbcTaskExecutionDao()); + return taskRepository; + } + + private TaskExecutionDao createJdbcTaskExecutionDao() { + JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource); + dao.setTablePrefix(tablePrefix); + return dao; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/MapTaskRepositoryFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/MapTaskRepositoryFactoryBean.java new file mode 100644 index 00000000..e3698a8d --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/MapTaskRepositoryFactoryBean.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; + +/** + * Automates the creation of a {@link SimpleTaskRepository} which will persist task + * execution data into an in memory map. This is meant for development + * purposes and not for production use. + * + * @author Glenn Renfro + */ +public class MapTaskRepositoryFactoryBean { + + private static final Log logger = LogFactory.getLog(MapTaskRepositoryFactoryBean.class); + + private TaskRepository taskRepository; + + public MapTaskRepositoryFactoryBean(){ + logger.debug(String.format("Creating SimpleTaskRepository that will use a %s", + MapTaskExecutionDao.class.getName())); + taskRepository = new SimpleTaskRepository(new MapTaskExecutionDao()); + } + + /** + * Returns the a simpleTaskRepository that utilizes a MapTaskExecutionDao + * @return instance of task repository. + */ + public TaskRepository getObject(){ + return taskRepository; + } +} 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 new file mode 100644 index 00000000..3d626726 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.TaskExecutionDao; +import org.springframework.util.Assert; + +/** + * Records the task execution information to the log and to TaskExecutionDao provided. + * @author Glenn Renfro + */ +public class SimpleTaskRepository implements TaskRepository { + + private final static Logger logger = LoggerFactory.getLogger(SimpleTaskRepository.class); + + private TaskExecutionDao taskExecutionDao; + + public SimpleTaskRepository(TaskExecutionDao taskExecutionDao){ + this.taskExecutionDao = taskExecutionDao; + } + + @Override + public void update(TaskExecution taskExecution) { + validateTaskExecution(taskExecution); + taskExecutionDao.updateTaskExecution(taskExecution); + logger.info("Updating: " + taskExecution.toString()); + } + + @Override + public void createTaskExecution(TaskExecution taskExecution) { + validateTaskExecution(taskExecution); + taskExecutionDao.saveTaskExecution(taskExecution); + logger.info("Creating: " + taskExecution.toString()); + } + + + /** + * Retrieves the taskExecutionDao associated with this repository. + * @return the taskExecutionDao + */ + public TaskExecutionDao getTaskExecutionDao() { + return taskExecutionDao; + } + + /** + * Validate TaskExecution. At a minimum a startTime. + * + * @param taskExecution the taskExecution to be evaluagted. + */ + private void validateTaskExecution(TaskExecution taskExecution) { + Assert.notNull(taskExecution, "taskExecution should not be null"); + Assert.notNull(taskExecution.getExecutionId(), "taskExecutionId should not be null"); + Assert.notNull(taskExecution.getStartTime(), "TaskExecution start time cannot be null."); + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializer.java new file mode 100644 index 00000000..b98a97e8 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 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.support; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.jdbc.support.MetaDataAccessException; + +/** + * @author Glenn Renfro + */ + +public class TaskDatabaseInitializer { + + private static final Log logger = LogFactory.getLog(TaskDatabaseInitializer.class); + + private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/" + + "cloud/task/schema-@@platform@@.sql"; + + /** + * Path to the SQL file to use to initialize the database schema. + */ + private static String schema = DEFAULT_SCHEMA_LOCATION; + + public static void initializeDatabase(DataSource dataSource, ResourceLoader resourceLoader) { + if (dataSource != null) { + String platform = getDatabaseType(dataSource); + if ("hsql".equals(platform)) { + platform = "hsqldb"; + } + if ("postgres".equals(platform)) { + platform = "postgresql"; + } + if ("oracle".equals(platform)) { + platform = "oracle10g"; + } + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + String schemaLocation = schema; + schemaLocation = schemaLocation.replace("@@platform@@", platform); + populator.addScript(resourceLoader.getResource(schemaLocation)); + populator.setContinueOnError(false); + logger.debug(String.format("Initializing task schema for %s database", + platform)); + DatabasePopulatorUtils.execute(populator, dataSource); + } + } + + private static String getDatabaseType(DataSource dataSource) { + try { + return DatabaseType.fromMetaData(dataSource).toString().toLowerCase(); + } + catch (MetaDataAccessException ex) { + throw new IllegalStateException("Unable to detect database type", 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 new file mode 100644 index 00000000..95223cf3 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes used for setting up and supporting a task repositories. + */ +package org.springframework.cloud.task.repository.support; diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql new file mode 100644 index 00000000..0901c276 --- /dev/null +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql @@ -0,0 +1,18 @@ + +CREATE TABLE TASK_EXECUTION ( + TASK_EXECUTION_ID VARCHAR(100) NOT NULL PRIMARY KEY , + START_TIME TIMESTAMP DEFAULT NULL , + END_TIME TIMESTAMP DEFAULT NULL , + TASK_NAME VARCHAR(100) , + EXIT_CODE INTEGER , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED TIMESTAMP , + STATUS_CODE VARCHAR(10) +); + +CREATE TABLE TASK_EXECUTION_PARAMS ( + TASK_EXECUTION_ID VARCHAR(100) NOT NULL , + TASK_PARAM VARCHAR(250) , + constraint TASK_EXEC_PARAMS_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/SimpleTaskConfigurationTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/SimpleTaskConfigurationTests.java index 8ad62029..9be462b0 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/SimpleTaskConfigurationTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/SimpleTaskConfigurationTests.java @@ -22,10 +22,12 @@ import static org.junit.Assert.assertThat; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.aop.framework.Advised; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.cloud.task.configuration.SimpleTaskConfiguration; -import org.springframework.cloud.task.repository.support.LoggerTaskRepository; import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.support.SimpleTaskRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -35,16 +37,17 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Glenn Renfro */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = SimpleTaskConfiguration.class) +@ContextConfiguration(classes = {SimpleTaskConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) public class SimpleTaskConfigurationTests { @Autowired private TaskRepository taskRepository; @Test - public void testRepository() { + public void testRepository() throws Exception { assertNotNull("testRepository should not be null", taskRepository); - assertThat(taskRepository, instanceOf(LoggerTaskRepository.class)); + TaskRepository clazz = (TaskRepository) ((Advised)taskRepository).getTargetSource().getTarget(); + assertThat(clazz, instanceOf(SimpleTaskRepository.class)); } } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/TaskHandlerDefaultTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/TaskHandlerDefaultTests.java index 89025747..19a4a6ff 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/TaskHandlerDefaultTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/TaskHandlerDefaultTests.java @@ -23,10 +23,11 @@ import org.aspectj.lang.JoinPoint; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.cloud.task.configuration.TaskHandler; import org.springframework.cloud.task.repository.TaskExecution; -import org.springframework.cloud.task.util.LoggerTestUtils; import org.springframework.cloud.task.util.TestDefaultConfiguration; +import org.springframework.cloud.task.util.TestVerifierUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -37,7 +38,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Glenn Renfro */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = TestDefaultConfiguration.class) +@ContextConfiguration(classes = {TestDefaultConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) public class TaskHandlerDefaultTests { @Autowired @@ -49,9 +50,9 @@ public class TaskHandlerDefaultTests { @Test public void testTaskException() { taskHandler.beforeCommandLineRunner(joinPoint); - final Appender mockAppender = LoggerTestUtils.getMockAppender(); + final Appender mockAppender = TestVerifierUtils.getMockAppender(); taskHandler.logExceptionCommandLineRunner(joinPoint); - LoggerTestUtils.verifyLogEntryExists(mockAppender, + TestVerifierUtils.verifyLogEntryExists(mockAppender, "Updating: TaskExecution{executionId='" + taskHandler.getTaskExecution().getExecutionId()); TaskExecution taskExecution = taskHandler.getTaskExecution(); @@ -61,9 +62,9 @@ public class TaskHandlerDefaultTests { @Test public void testTaskCreate() { - final Appender mockAppender = LoggerTestUtils.getMockAppender(); + final Appender mockAppender = TestVerifierUtils.getMockAppender(); taskHandler.beforeCommandLineRunner(joinPoint); - LoggerTestUtils.verifyLogEntryExists(mockAppender, + TestVerifierUtils.verifyLogEntryExists(mockAppender, "Creating: TaskExecution{executionId='" + taskHandler.getTaskExecution().getExecutionId()); assertEquals("Create should report that exit code is zero", @@ -74,9 +75,9 @@ public class TaskHandlerDefaultTests { @Test public void testTaskUpdate() { taskHandler.beforeCommandLineRunner(joinPoint); - final Appender mockAppender = LoggerTestUtils.getMockAppender(); + final Appender mockAppender = TestVerifierUtils.getMockAppender(); taskHandler.afterReturnCommandLineRunner(joinPoint); - LoggerTestUtils.verifyLogEntryExists(mockAppender, + TestVerifierUtils.verifyLogEntryExists(mockAppender, "Updating: TaskExecution{executionId='" + taskHandler.getTaskExecution().getExecutionId()); assertEquals("Update should report that exit code is zero", 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 new file mode 100644 index 00000000..9624404c --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015 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 javax.sql.DataSource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.cloud.task.annotation.EnableTask; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.util.TestDBUtils; +import org.springframework.cloud.task.util.TestVerifierUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.dao.DuplicateKeyException; + +/** + * Executes unit tests on JdbcTaskExecutionDao. + * + * @author Glenn Renfro + */ +public class JdbcTaskExecutionDaoTests { + + private DataSource dataSource; + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + dataSource = this.context.getBean(DataSource.class); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void saveTaskExecution() { + JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource); + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.saveTaskExecution(expectedTaskExecution); + + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + TestDBUtils.getTaskExecutionFromDB(dataSource, expectedTaskExecution.getExecutionId())); + } + + @Test(expected = DuplicateKeyException.class) + public void duplicateSaveTaskExecution() { + JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource); + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.saveTaskExecution(expectedTaskExecution); + dao.saveTaskExecution(expectedTaskExecution); + } + + @Test + public void updateTaskExecution() { + JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource); + + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.saveTaskExecution(expectedTaskExecution); + dao.updateTaskExecution(expectedTaskExecution); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + TestDBUtils.getTaskExecutionFromDB(dataSource, expectedTaskExecution.getExecutionId())); + } + + @Test(expected = IllegalStateException.class) + public void updateTaskExecutionWithNoCreate() { + JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource); + + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.updateTaskExecution(expectedTaskExecution); + } + + @EnableTask + protected static class TestConfiguration { + } +} 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 new file mode 100644 index 00000000..a9f00cca --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDaoTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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 static org.junit.Assert.assertNotNull; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.util.TestVerifierUtils; + +/** + * Executes unit tests on MapTaskExecutionDaoTests. + * @author Glenn Renfro + */ +public class MapTaskExecutionDaoTests { + + + @Test + public void saveTaskExecution(){ + MapTaskExecutionDao dao = new MapTaskExecutionDao(); + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.saveTaskExecution(expectedTaskExecution); + Map taskExecutionMap = dao.getTaskExecutions(); + assertNotNull("taskExecutionMap must not be null", taskExecutionMap); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + taskExecutionMap.get(expectedTaskExecution.getExecutionId())); + } + + @Test + public void updateTaskExecution(){ + MapTaskExecutionDao dao = new MapTaskExecutionDao(); + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + dao.saveTaskExecution(expectedTaskExecution); + dao.updateTaskExecution(expectedTaskExecution); + Map taskExecutionMap = dao.getTaskExecutions(); + assertNotNull("taskExecutionMap must not be null", taskExecutionMap); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + taskExecutionMap.get(expectedTaskExecution.getExecutionId())); + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java new file mode 100644 index 00000000..388226f9 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015 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.support; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.task.repository.support.DatabaseType.HSQL; +import static org.springframework.cloud.task.repository.support.DatabaseType.ORACLE; +import static org.springframework.cloud.task.repository.support.DatabaseType.POSTGRES; +import static org.springframework.cloud.task.repository.support.DatabaseType.fromProductName; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; + +import javax.sql.DataSource; + +import org.junit.Test; + +/** + * Tests that the correct database names are selected from datasource metadata. + * + * @author Lucas Ward + * @author Will Schipp + * @author Glenn Renfro + * + */ +public class DatabaseTypeTests { + + @Test + public void testFromProductName() { + assertEquals(HSQL, fromProductName("HSQL Database Engine")); + assertEquals(ORACLE, fromProductName("Oracle")); + assertEquals(POSTGRES, fromProductName("PostgreSQL")); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidProductName() { + fromProductName("bad product name"); + } + + @Test + public void testFromMetaDataForHsql() throws Exception { + DataSource ds = getMockDataSource("HSQL Database Engine"); + assertEquals(HSQL, DatabaseType.fromMetaData(ds)); + } + + @Test + public void testFromMetaDataForOracle() throws Exception { + DataSource ds = getMockDataSource("Oracle"); + assertEquals(ORACLE, DatabaseType.fromMetaData(ds)); + } + + @Test + public void testFromMetaDataForPostgres() throws Exception { + DataSource ds = getMockDataSource("PostgreSQL"); + assertEquals(POSTGRES, DatabaseType.fromMetaData(ds)); + } + + public DataSource getMockDataSource(String databaseProductName) throws Exception { + DatabaseMetaData dmd = mock(DatabaseMetaData.class); + DataSource ds = mock(DataSource.class); + Connection con = mock(Connection.class); + when(ds.getConnection()).thenReturn(con); + when(con.getMetaData()).thenReturn(dmd); + when(dmd.getDatabaseProductName()).thenReturn(databaseProductName); + return ds; + } + + public DataSource getMockDataSource(Exception e) throws Exception { + DataSource ds = mock(DataSource.class); + when(ds.getConnection()).thenReturn(null); + return ds; + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryJdbcTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryJdbcTests.java new file mode 100644 index 00000000..efaafeaa --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryJdbcTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015 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.support; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.cloud.task.annotation.EnableTask; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.util.TaskExecutionCreator; +import org.springframework.cloud.task.util.TestDBUtils; +import org.springframework.cloud.task.util.TestVerifierUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * Tests for the SimpleTaskRepository that uses JDBC as a datastore. + * + * @author Glenn Renfro. + */ +public class SimpleTaskRepositoryJdbcTests { + + private TaskRepository taskRepository; + + private DataSource dataSource; + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + dataSource = this.context.getBean(DataSource.class); + JdbcTaskRepositoryFactoryBean factoryBean = + new JdbcTaskRepositoryFactoryBean(dataSource); + taskRepository = factoryBean.getObject(); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testCreateTaskExecutionNoParam() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + TaskExecution actualTaskExecution = TestDBUtils.getTaskExecutionFromDB(dataSource, + expectedTaskExecution.getExecutionId()); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution); + } + + @Test + public void testCreateTaskExecutionWithParam() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionWithParams(taskRepository); + TaskExecution actualTaskExecution = TestDBUtils.getTaskExecutionFromDB( + dataSource, expectedTaskExecution.getExecutionId()); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution); + } + + @Test + public void testUpdateTaskExecution() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + expectedTaskExecution = TaskExecutionCreator.updateTaskExecution(taskRepository, + expectedTaskExecution.getExecutionId()); + TaskExecution actualTaskExecution = TestDBUtils.getTaskExecutionFromDB( + dataSource, expectedTaskExecution.getExecutionId()); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution); + } + + @EnableTask + protected static class TestConfiguration { + } + +} + diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/LoggerTaskRepositoryTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryLoggerTests.java similarity index 56% rename from spring-cloud-task-core/src/test/java/org/springframework/cloud/task/LoggerTaskRepositoryTests.java rename to spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryLoggerTests.java index d52505e4..95ad4277 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/LoggerTaskRepositoryTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryLoggerTests.java @@ -14,58 +14,50 @@ * limitations under the License. */ -package org.springframework.cloud.task; - -import java.util.UUID; +package org.springframework.cloud.task.repository.support; import ch.qos.logback.core.Appender; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.TaskRepository; -import org.springframework.cloud.task.util.LoggerTestUtils; +import org.springframework.cloud.task.util.TaskExecutionCreator; import org.springframework.cloud.task.util.TestDefaultConfiguration; +import org.springframework.cloud.task.util.TestVerifierUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * Verifies that the LoggerRepository has correct prefixes written to logs. + * Verifies that the SimpleTaskRepository has correct prefixes written to logs. * @author Glenn Renfro */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TestDefaultConfiguration.class) -public class LoggerTaskRepositoryTests { +public class SimpleTaskRepositoryLoggerTests { @Autowired private TaskRepository taskRepository; - private TaskExecution taskExecution; - - private String uuId; - - @Before - public void setup(){ - taskExecution = new TaskExecution(); - uuId = UUID.randomUUID().toString(); - taskExecution.setExecutionId(uuId); - } - @Test public void testCreateTaskExecution() { - final Appender mockAppender = LoggerTestUtils.getMockAppender(); - taskRepository.createTaskExecution(taskExecution); - LoggerTestUtils.verifyLogEntryExists(mockAppender, - "Creating: TaskExecution{executionId='" + uuId); + final Appender mockAppender = TestVerifierUtils.getMockAppender(); + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + TestVerifierUtils.verifyLogEntryExists(mockAppender, + "Creating: TaskExecution{executionId='" + expectedTaskExecution.getExecutionId()); } @Test public void testTaskUpdate() { - final Appender mockAppender = LoggerTestUtils.getMockAppender(); - taskRepository.update(taskExecution); - LoggerTestUtils.verifyLogEntryExists(mockAppender, - "Updating: TaskExecution{executionId='" + uuId); + final Appender mockAppender = TestVerifierUtils.getMockAppender(); + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + TaskExecutionCreator.updateTaskExecution(taskRepository, + expectedTaskExecution.getExecutionId()); + TestVerifierUtils.verifyLogEntryExists(mockAppender, + "Updating: TaskExecution{executionId='" + + expectedTaskExecution.getExecutionId()); } } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java new file mode 100644 index 00000000..fc54ca6c --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015 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.support; + +import static org.springframework.test.util.AssertionErrors.assertTrue; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; +import org.springframework.cloud.task.util.TaskExecutionCreator; +import org.springframework.cloud.task.util.TestVerifierUtils; + +/** + * Tests for the SimpleTaskRepository that uses Map as a datastore. + * @author Glenn Renfro. + */ +public class SimpleTaskRepositoryMapTests { + + private TaskRepository taskRepository; + + @Before + public void setUp() { + MapTaskRepositoryFactoryBean factoryBean = + new MapTaskRepositoryFactoryBean(); + taskRepository = factoryBean.getObject(); + } + + @Test + public void testCreateTaskExecutionNoParam() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + getSingleTaskExecutionFromMapRepository(taskRepository, + expectedTaskExecution.getExecutionId())); + } + + @Test + public void testCreateTaskExecutionWithParam() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionWithParams(taskRepository); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + getSingleTaskExecutionFromMapRepository(taskRepository, + expectedTaskExecution.getExecutionId())); + } + + @Test + public void testUpdateTaskExecution() { + TaskExecution expectedTaskExecution = + TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); + expectedTaskExecution = TaskExecutionCreator.updateTaskExecution(taskRepository, + expectedTaskExecution.getExecutionId()); + TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, + getSingleTaskExecutionFromMapRepository(taskRepository, + expectedTaskExecution.getExecutionId())); + } + + private TaskExecution getSingleTaskExecutionFromMapRepository( + TaskRepository repository, String taskExecutionId){ + Map taskMap = ((MapTaskExecutionDao) + ((SimpleTaskRepository)taskRepository).getTaskExecutionDao()).getTaskExecutions(); + assertTrue("taskExecutionId must be in MapTaskExecutionRepository", + taskMap.containsKey(taskExecutionId)); + return taskMap.get(taskExecutionId); + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java new file mode 100644 index 00000000..4636f9d3 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 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.support; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.cloud.task.annotation.EnableTask; +import org.springframework.cloud.task.configuration.TaskConfigurer; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Verifies that task initialization occurs properly. + * + * @author Glenn Renfro + */ +public class TaskDatabaseInitializerTests { + private AnnotationConfigApplicationContext context; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testDefaultContext() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + this.context.register( TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertEquals(0, new JdbcTemplate(this.context.getBean(DataSource.class)) + .queryForList("select * from TASK_EXECUTION").size()); + } + +// @Test + public void testNoDatabase() throws Exception { + SimpleTaskRepository repository = new SimpleTaskRepository(new MapTaskExecutionDao()); + this.context.refresh(); + assertThat(repository.getTaskExecutionDao(), instanceOf(MapTaskExecutionDao.class)); + MapTaskExecutionDao dao = (MapTaskExecutionDao) repository.getTaskExecutionDao(); + assertEquals(0, dao.getTaskExecutions().size()); + } + + @Test + public void testNoTaskConfiguration() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(EmptyConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertEquals(0, this.context.getBeanNamesForType(SimpleTaskRepository.class).length); + } + + @Test(expected = BeanCreationException.class) + public void testMultipleDataSourcesContext() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + this.context.register( TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + DataSource dataSource = mock(DataSource.class); + context.getBeanFactory().registerSingleton("mockDataSource", dataSource); + this.context.refresh(); + } + + @EnableTask + protected static class TestConfiguration { + } + + @EnableTask + protected static class TestCustomConfiguration implements TaskConfigurer { + @Override + public TaskRepository getTaskRepository() { + return new SimpleTaskRepository(new MapTaskExecutionDao()); + } + } + + @Configuration + protected static class EmptyConfiguration { + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskRepositoryFactoryBeanTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskRepositoryFactoryBeanTests.java new file mode 100644 index 00000000..b615fd68 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskRepositoryFactoryBeanTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.support; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; + +/** + * Tests that the TaskRepositoryFactoryBeans produce the correct repositories. + * + * @author Glenn Renfro + */ + +public class TaskRepositoryFactoryBeanTests { + + @Test + public void testJdbcTaskRepositoryFactoryBean() { + DataSource dataSource = mock(DataSource.class); + JdbcTaskRepositoryFactoryBean factory = new JdbcTaskRepositoryFactoryBean(dataSource); + TaskRepository repository = factory.getObject(); + assertThat(repository, instanceOf(SimpleTaskRepository.class)); + assertThat(((SimpleTaskRepository) repository).getTaskExecutionDao(), + instanceOf(JdbcTaskExecutionDao.class)); + } + + @Test + public void testMapTaskRepositoryFactoryBean() { + MapTaskRepositoryFactoryBean factory = new MapTaskRepositoryFactoryBean(); + TaskRepository repository = factory.getObject(); + assertThat(repository, instanceOf(SimpleTaskRepository.class)); + assertThat(((SimpleTaskRepository) repository).getTaskExecutionDao(), + instanceOf(MapTaskExecutionDao.class)); + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/LoggerTestUtils.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/LoggerTestUtils.java deleted file mode 100644 index c77dad11..00000000 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/LoggerTestUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015 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.util; - -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import ch.qos.logback.classic.spi.LoggingEvent; -import ch.qos.logback.core.Appender; -import org.mockito.ArgumentMatcher; -import org.slf4j.LoggerFactory; - -/** - * Offers utils to test the log results produced by the code being tested. - * - * @author Glenn Renfro - */ -public class LoggerTestUtils { - - /** - * Creates a mock {@link Appender} to be added to the root logger. - * @return reference to the mock appender. - */ - public static Appender getMockAppender(){ - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); - final Appender mockAppender = mock(Appender.class); - when(mockAppender.getName()).thenReturn("MOCK"); - root.addAppender(mockAppender); - return mockAppender; - } - - /** - * Verifies that the log sample is contained within the content that was written - * to the mock appender. - * @param mockAppender The appender that is associated with the test. - * @param logSample The string to search for in the log entry. - */ - public static void verifyLogEntryExists(Appender mockAppender, final String logSample){ - verify(mockAppender).doAppend(argThat(new ArgumentMatcher() { - @Override - public boolean matches(final Object argument) { - return ((LoggingEvent)argument).getFormattedMessage().contains(logSample); - } - })); - } -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TaskExecutionCreator.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TaskExecutionCreator.java new file mode 100644 index 00000000..20c29141 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TaskExecutionCreator.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskRepository; + +/** + * Offers ability to create TaskExecutions for the test suite. + * + * @author Glenn Renfro + */ +public class TaskExecutionCreator { + + /** + * Creates a sample TaskExecution and stores it in the taskRepository. + * + * @param taskRepository the taskRepository where the taskExecution should be stored. + * @return the taskExecution created. + */ + public static TaskExecution createAndStoreTaskExecutionNoParams(TaskRepository taskRepository) { + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + taskRepository.createTaskExecution(expectedTaskExecution); + return expectedTaskExecution; + } + + /** + * Creates a sample TaskExecution and stores it in the taskRepository with params. + * + * @param taskRepository the taskRepository where the taskExecution should be stored. + * @return the taskExecution created. + */ + public static TaskExecution createAndStoreTaskExecutionWithParams(TaskRepository taskRepository) { + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + List params = new ArrayList(); + params.add(UUID.randomUUID().toString()); + params.add(UUID.randomUUID().toString()); + expectedTaskExecution.setParameters(params); + taskRepository.createTaskExecution(expectedTaskExecution); + return expectedTaskExecution; + } + + /** + * Updates a sample TaskExecution in the taskRepository. + * + * @param taskRepository the taskRepository where the taskExecution should be updated. + * @return the taskExecution created. + */ + public static TaskExecution updateTaskExecution(TaskRepository taskRepository, + String taskExecutionId) { + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + expectedTaskExecution.setExecutionId(taskExecutionId); + taskRepository.update(expectedTaskExecution); + return expectedTaskExecution; + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDBUtils.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDBUtils.java new file mode 100644 index 00000000..51eced62 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDBUtils.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015 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.util; + +import static org.junit.Assert.assertEquals; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +/** + * Provides a suite of tools that allow tests the ability to retrieve results from a + * relational database. + * + * @author Glenn Renfro + */ +public class TestDBUtils { + + /** + * Retrieves the TaskExecution from the datasource + * + * @param dataSource The datasource from which to retrieve the taskExecution + * @param taskExecutionId The id of the task to search . + * @return taskExecution + */ + public static TaskExecution getTaskExecutionFromDB(DataSource dataSource, + String taskExecutionId) { + String sql = "SELECT * FROM TASK_EXECUTION WHERE " + + "TASK_EXECUTION_ID = '" + + taskExecutionId + "'"; + + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + List rows = jdbcTemplate.query(sql, new RowMapper(){ + @Override + public TaskExecution mapRow(ResultSet rs, int rownumber) throws SQLException { + TaskExecution taskExecution=new TaskExecution(); + taskExecution.setExecutionId(rs.getString(1)); + taskExecution.setStartTime(rs.getTimestamp("START_TIME")); + taskExecution.setEndTime(rs.getTimestamp("END_TIME")); + taskExecution.setExitCode(rs.getInt("EXIT_CODE")); + taskExecution.setExitMessage(rs.getString("EXIT_MESSAGE")); + taskExecution.setStatusCode(rs.getString("STATUS_CODE")); + taskExecution.setTaskName(rs.getString("TASK_NAME")); + return taskExecution; + } + }); + assertEquals("only one row should be returned", 1, rows.size()); + TaskExecution taskExecution = rows.get(0); + + populateParamsToDB(dataSource, taskExecution); + return taskExecution; + } + + private static void populateParamsToDB(DataSource dataSource, TaskExecution taskExecution) { + String sql = "SELECT * FROM TASK_EXECUTION_PARAMS WHERE TASK_EXECUTION_ID = '" + + taskExecution.getExecutionId() + "'"; + + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + List> rows = jdbcTemplate.queryForList(sql); + List params = new ArrayList<>(); + for (Map row : rows) { + params.add((String) row.get("TASK_PARAM")); + } + taskExecution.setParameters(params); + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java index 82cddd65..59b3b30e 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java @@ -18,8 +18,9 @@ package org.springframework.cloud.task.util; import org.aspectj.lang.JoinPoint; import org.springframework.cloud.task.configuration.TaskHandler; -import org.springframework.cloud.task.repository.support.LoggerTaskRepository; import org.springframework.cloud.task.repository.TaskRepository; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; +import org.springframework.cloud.task.repository.support.SimpleTaskRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,7 +34,7 @@ public class TestDefaultConfiguration { @Bean public TaskRepository taskRepository(){ - return new LoggerTaskRepository(); + return new SimpleTaskRepository(new MapTaskExecutionDao()); } @Bean diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java new file mode 100644 index 00000000..13286525 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015 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.util; + +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.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import org.mockito.ArgumentMatcher; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * Offers utils to test the results produced by the code being tested. + * + * @author Glenn Renfro + */ +public class TestVerifierUtils { + + /** + * Creates a mock {@link Appender} to be added to the root logger. + * + * @return reference to the mock appender. + */ + public static Appender getMockAppender() { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + final Appender mockAppender = mock(Appender.class); + when(mockAppender.getName()).thenReturn("MOCK"); + root.addAppender(mockAppender); + return mockAppender; + } + + /** + * Verifies that the log sample is contained within the content that was written + * to the mock appender. + * + * @param mockAppender The appender that is associated with the test. + * @param logSample The string to search for in the log entry. + */ + public static void verifyLogEntryExists(Appender mockAppender, final String logSample) { + verify(mockAppender).doAppend(argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + return ((LoggingEvent) argument).getFormattedMessage().contains(logSample); + } + })); + } + + /** + * Creates a fully populated TaskExecution (except params) for testing. + * + * @return + */ + public static TaskExecution createSampleTaskExecutionNoParam() { + Random randomGenerator = new Random(); + int exitCode = randomGenerator.nextInt(); + Date startTime = new Date(); + Date endTime = new Date(); + String executionId = UUID.randomUUID().toString(); + String taskName = UUID.randomUUID().toString(); + String exitMessage = UUID.randomUUID().toString(); + String statusCode = UUID.randomUUID().toString().substring(0, 9); + + return new TaskExecution(executionId, exitCode, taskName, + startTime, endTime, statusCode, + exitMessage, new ArrayList()); + } + + /** + * Verifies that all the fields in between the expected and actual are the same; + * + * @param expectedTaskExecution The expected value for the task execution. + * @param actualTaskExecution The actual value for the task execution. + */ + public static void verifyTaskExecution(TaskExecution expectedTaskExecution, + TaskExecution actualTaskExecution) { + assertEquals("taskExecutionId must be equal", expectedTaskExecution.getExecutionId(), + actualTaskExecution.getExecutionId()); + assertEquals("startTime must be equal", + expectedTaskExecution.getStartTime(), + actualTaskExecution.getStartTime()); + assertEquals("endTime must be equal", + expectedTaskExecution.getEndTime(), + actualTaskExecution.getEndTime()); + assertEquals("exitCode must be equal", + expectedTaskExecution.getExitCode(), + actualTaskExecution.getExitCode()); + assertEquals("taskName must be equal", + expectedTaskExecution.getTaskName(), + actualTaskExecution.getTaskName()); + assertEquals("exitMessage must be equal", + expectedTaskExecution.getExitMessage(), + actualTaskExecution.getExitMessage()); + assertEquals("statusCode must be equal", + expectedTaskExecution.getStatusCode(), + actualTaskExecution.getStatusCode()); + if (expectedTaskExecution.getParameters() != null) { + assertNotNull("parameters should not be null", + actualTaskExecution.getParameters()); + assertEquals("parameters result set count should match expected count", + expectedTaskExecution.getParameters().size(), + actualTaskExecution.getParameters().size()); + } + else { + assertNull("parameters should be null", actualTaskExecution.getParameters()); + } + Set params = new HashSet(); + for (String param : expectedTaskExecution.getParameters()) { + params.add(param); + } + for (String param : actualTaskExecution.getParameters()) { + assertTrue("param must exist in the repository", params.contains(param)); + } + } + + +} diff --git a/spring-cloud-task-samples/pom.xml b/spring-cloud-task-samples/pom.xml index 53d711a3..cd0a96c4 100644 --- a/spring-cloud-task-samples/pom.xml +++ b/spring-cloud-task-samples/pom.xml @@ -11,4 +11,24 @@ spring-cloud-task-samples + + + + + + maven-deploy-plugin + + true + + + + maven-install-plugin + + true + + + + + +