diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml index d9efd933..e51ec135 100755 --- a/spring-cloud-task-core/pom.xml +++ b/spring-cloud-task-core/pom.xml @@ -67,6 +67,14 @@ org.springframework.batch spring-batch-infrastructure + + org.springframework.batch + spring-batch-core + + + org.springframework.data + spring-data-commons + diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java index db951cd8..7cb4ac5d 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java @@ -19,6 +19,9 @@ package org.springframework.cloud.task.repository; import java.util.List; import java.util.Set; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + /** * Offers methods that allow users to query the task executions that are available. * @@ -56,7 +59,14 @@ public interface TaskExplorer { * @param taskName the name of the task to be searched * @return the number of running tasks that have the taskname specified */ - public long getTaskExecutionCount(String taskName); + public long getTaskExecutionCountByTaskName(String taskName); + + /** + * Retrieves current number of task executions. + * + * @return current number of task executions. + */ + long getTaskExecutionCount(); /** * Get a collection/page of executions @@ -68,5 +78,13 @@ public interface TaskExplorer { */ public List getTaskExecutionsByName(String taskName, int start, int count); + /** + * Retrieves all the task executions within the pageable constraints sorted by + * start date descending, taskExecution id descending. + * + * @param pageable the constraints for the search + * @return page containing the results from the search + */ + public Page findAll(Pageable pageable); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java index 4c494dae..4a2be0c4 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java @@ -23,12 +23,20 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import javax.sql.DataSource; +import org.springframework.batch.item.database.Order; import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.cloud.task.repository.database.support.SqlPagingQueryProviderFactoryBean; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowCallbackHandler; @@ -44,6 +52,12 @@ import org.springframework.util.StringUtils; public class JdbcTaskExecutionDao implements TaskExecutionDao { + public static String SELECT_CLAUSE = "TASK_EXECUTION_ID, " + + "START_TIME, END_TIME, TASK_NAME, EXIT_CODE, " + + "EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE "; + + public static String FROM_CLAUSE = "%PREFIX%EXECUTION"; + 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 (?, ?, ?, ?, ?, ?, ?, ?)"; @@ -68,6 +82,9 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { + "TASK_PARAM from %PREFIX%EXECUTION_PARAMS where TASK_EXECUTION_ID = ?"; private static final String TASK_EXECUTION_COUNT = "SELECT COUNT(*) FROM " + + "%PREFIX%EXECUTION "; + + private static final String TASK_EXECUTION_COUNT_BY_NAME = "SELECT COUNT(*) FROM " + "%PREFIX%EXECUTION where TASK_NAME = ?"; private static final String FIND_RUNNING_TASK_EXECUTIONS = "SELECT TASK_EXECUTION_ID, " @@ -76,7 +93,7 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { + "from %PREFIX%EXECUTION where TASK_NAME = ? AND END_TIME IS NULL " + "order by TASK_EXECUTION_ID"; - private static final String FIND_TASK_EXECUTIONS = "SELECT TASK_EXECUTION_ID, " + private static final String FIND_TASK_EXECUTIONS_BY_NAME = "SELECT TASK_EXECUTION_ID, " + "START_TIME, END_TIME, TASK_NAME, EXIT_CODE, " + "EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE " + "from %PREFIX%EXECUTION where TASK_NAME = ? " @@ -91,9 +108,18 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { private JdbcOperations jdbcTemplate; + private DataSource dataSource; + + Map orderMap; + public JdbcTaskExecutionDao(DataSource dataSource) { Assert.notNull(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource); + this.dataSource = dataSource; + orderMap = new TreeMap<>(); + orderMap.put("START_TIME", Order.DESCENDING); + orderMap.put("TASK_EXECUTION_ID", Order.DESCENDING); + } @Override @@ -156,15 +182,25 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { } @Override - public long getTaskExecutionCount(String taskName) { + public long getTaskExecutionCountByTaskName(String taskName) { try { return jdbcTemplate.queryForObject( - getQuery(TASK_EXECUTION_COUNT), new Object[] { taskName }, Long.class); + getQuery(TASK_EXECUTION_COUNT_BY_NAME), new Object[] { taskName }, Long.class); } catch (EmptyResultDataAccessException e) { return 0; } + } + @Override + public long getTaskExecutionCount() { + try { + return jdbcTemplate.queryForObject( + getQuery(TASK_EXECUTION_COUNT), new Object[] { }, Long.class); + } + catch (EmptyResultDataAccessException e) { + return 0; + } } @Override @@ -179,8 +215,8 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { @Override public List getTaskExecutionsByName(String taskName, final int start, final int count) { - return jdbcTemplate.query(getQuery(FIND_TASK_EXECUTIONS), - new Object[]{ taskName, count, start + 1 }, new TaskExecutionRowMapper()); + return jdbcTemplate.query(getQuery(FIND_TASK_EXECUTIONS_BY_NAME), + new Object[]{ taskName, count, start }, new TaskExecutionRowMapper()); } @Override @@ -188,6 +224,30 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { return jdbcTemplate.queryForList(getQuery(FIND_TASK_NAMES), String.class); } + @Override + public Page findAll(Pageable pageable) { + + SqlPagingQueryProviderFactoryBean factoryBean = new SqlPagingQueryProviderFactoryBean(); + factoryBean.setSelectClause(SELECT_CLAUSE); + factoryBean.setFromClause(FROM_CLAUSE); + factoryBean.setSortKeys(orderMap); + factoryBean.setDataSource(dataSource); + PagingQueryProvider pagingQueryProvider = null; + try { + pagingQueryProvider = factoryBean.getObject(); + pagingQueryProvider.init(dataSource); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + String query = pagingQueryProvider.getPageQuery(pageable); + List resultList = jdbcTemplate.query( + getQuery(query), + new Object[]{ }, + new TaskExecutionRowMapper()); + return new PageImpl(resultList, pageable, getTaskExecutionCount()); + } + private String getQuery(String base) { return StringUtils.replace(base, "%PREFIX%", tablePrefix); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java index cacc171d..7838f914 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java @@ -27,6 +27,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; /** * Stores Task Execution Information to a in-memory map. @@ -57,7 +60,7 @@ public class MapTaskExecutionDao implements TaskExecutionDao { } @Override - public long getTaskExecutionCount(String taskName) { + public long getTaskExecutionCountByTaskName(String taskName) { int count = 0; for (Map.Entry entry : taskExecutions.entrySet()) { if (entry.getValue().getTaskName().equals(taskName)) { @@ -67,6 +70,11 @@ public class MapTaskExecutionDao implements TaskExecutionDao { return count; } + @Override + public long getTaskExecutionCount() { + return taskExecutions.size(); + } + @Override public Set findRunningTaskExecutions(String taskName) { Set result = getTaskExecutionTreeSet(); @@ -101,6 +109,19 @@ public class MapTaskExecutionDao implements TaskExecutionDao { return new ArrayList(result); } + @Override + public Page findAll(Pageable pageable) { + TreeSet sortedSet = getTaskExecutionTreeSet(); + sortedSet.addAll(taskExecutions.values()); + List result = new ArrayList<>(sortedSet.descendingSet()); + int toIndex = (pageable.getOffset() + pageable.getPageSize() > result.size()) ? + result.size() : pageable.getOffset() + pageable.getPageSize(); + return new PageImpl( + result.subList(pageable.getOffset(), toIndex), + pageable, + getTaskExecutionCount()); + } + public Map getTaskExecutions() { return Collections.unmodifiableMap(taskExecutions); } @@ -109,7 +130,11 @@ public class MapTaskExecutionDao implements TaskExecutionDao { return new TreeSet(new Comparator() { @Override public int compare(TaskExecution e1, TaskExecution e2) { - return e1.getExecutionId().compareTo(e2.getExecutionId()); + int result = e1.getStartTime().compareTo(e2.getStartTime()); + if (result == 0){ + result = e1.getExecutionId().compareTo(e2.getExecutionId()); + } + return result; } }); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java index 17560c8c..6a272ba7 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Set; import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; /** * Data Access Object for task executions. @@ -54,9 +56,16 @@ public interface TaskExecutionDao { * Retrieves current number of task executions for a taskName. * * @param taskName the name of the task to search for in the repository. + * @return current number of task executions for the taskName. + */ + long getTaskExecutionCountByTaskName(String taskName); + + /** + * Retrieves current number of task executions. + * * @return current number of task executions. */ - long getTaskExecutionCount(String taskName); + long getTaskExecutionCount(); /** * Retrieves a set of task executions that are running for a taskName. @@ -83,4 +92,12 @@ public interface TaskExecutionDao { * @return a list of distinct task names from the task repository.. */ public List getTaskNames(); + + /** + * Retrieves all the task executions within the pageable constraints. + * @param pageable the constraints for the search + * @return page containing the results from the search + */ + + public Page findAll(Pageable pageable); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/PagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/PagingQueryProvider.java new file mode 100644 index 00000000..b4eef127 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/PagingQueryProvider.java @@ -0,0 +1,68 @@ +/* + * 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.database; + +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.batch.item.database.Order; +import org.springframework.data.domain.Pageable; + +/** + * Interface defining the functionality to be provided for generating paging queries. + * @author Glenn Renfro + */ +public interface PagingQueryProvider { + + /** + * Initialize the query provider using the provided {@link DataSource} if necessary. + * + * @param dataSource DataSource to use for any initialization + */ + void init(DataSource dataSource) throws Exception; + + /** + * The number of parameters that are declared in the query + * @return number of parameters + */ + int getParameterCount(); + + /** + * Indicate whether the generated queries use named parameter syntax. + * + * @return true if named parameter syntax is used + */ + boolean isUsingNamedParameters(); + + /** + * The sort keys. A Map of the columns that make up the key and a Boolean indicating ascending or descending + * (ascending = true). + * + * @return the sort keys used to order the query + */ + Map getSortKeys(); + + /** + * + * Generate the query that will provide the jump to item query. + * + * @param pageable the coordinates to pull the next page from the datasource + * @return the generated query + */ + String getPageQuery(Pageable pageable); +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java new file mode 100644 index 00000000..8c6c18d3 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/AbstractSqlPagingQueryProvider.java @@ -0,0 +1,169 @@ +/* + * 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.database.support; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.batch.item.database.JdbcParameterUtils; +import org.springframework.batch.item.database.Order; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Abstract SQL Paging Query Provider to serve as a base class for all provided + * SQL paging query providers. + * + * Any implementation must provide a way to specify the select clause, from + * clause and optionally a where clause. It is recommended that there should be an index for + * the sort key to provide better performance. + * + * Provides properties and preparation for the mandatory "selectClause" and + * "fromClause" as well as for the optional "whereClause". + * + * @author Glenn Renfro + */ +public abstract class AbstractSqlPagingQueryProvider implements PagingQueryProvider { + + private String selectClause; + + private String fromClause; + + private String whereClause; + + private Map sortKeys = new LinkedHashMap(); + + private int parameterCount; + + private boolean usingNamedParameters; + + /** + * @param selectClause SELECT clause part of SQL query string + */ + public void setSelectClause(String selectClause) { + this.selectClause = removeKeyWord("select", selectClause); + } + + /** + * + * @return SQL SELECT clause part of SQL query string + */ + protected String getSelectClause() { + return selectClause; + } + + /** + * @param fromClause FROM clause part of SQL query string + */ + public void setFromClause(String fromClause) { + this.fromClause = removeKeyWord("from", fromClause); + } + + /** + * + * @return SQL FROM clause part of SQL query string + */ + protected String getFromClause() { + return fromClause; + } + + /** + * @param whereClause WHERE clause part of SQL query string + */ + public void setWhereClause(String whereClause) { + if (StringUtils.hasText(whereClause)) { + this.whereClause = removeKeyWord("where", whereClause); + } + else { + this.whereClause = null; + } + } + + /** + * + * @return SQL WHERE clause part of SQL query string + */ + protected String getWhereClause() { + return whereClause; + } + + /** + * @param sortKeys key to use to sort and limit page content + */ + public void setSortKeys(Map sortKeys) { + this.sortKeys = sortKeys; + } + + /** + * A Map<String, Order> of sort columns as the key and {@link Order} for ascending/descending. + * + * @return sortKey key to use to sort and limit page content + */ + @Override + public Map getSortKeys() { + return sortKeys; + } + + @Override + public int getParameterCount() { + return parameterCount; + } + + @Override + public boolean isUsingNamedParameters() { + return usingNamedParameters; + } + + @Override + public void init(DataSource dataSource) throws Exception { + Assert.notNull(dataSource); + Assert.hasLength(selectClause, "selectClause must be specified"); + Assert.hasLength(fromClause, "fromClause must be specified"); + Assert.notEmpty(sortKeys, "sortKey must be specified"); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(selectClause); + sql.append(" FROM ").append(fromClause); + if (whereClause != null) { + sql.append(" WHERE ").append(whereClause); + } + List namedParameters = new ArrayList(); + parameterCount = JdbcParameterUtils.countParameterPlaceholders(sql.toString(), namedParameters); + if (namedParameters.size() > 0) { + if (parameterCount != namedParameters.size()) { + throw new InvalidDataAccessApiUsageException( + "You can't use both named parameters and classic \"?\" placeholders: " + sql); + } + usingNamedParameters = true; + } + } + private String removeKeyWord(String keyWord, String clause) { + String temp = clause.trim(); + String keyWordString = keyWord + " "; + if (temp.toLowerCase().startsWith(keyWordString) && temp.length() > keyWordString.length()) { + return temp.substring(keyWordString.length()); + } + else { + return temp; + } + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/HsqlPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/HsqlPagingQueryProvider.java new file mode 100644 index 00000000..92ee5ee4 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/HsqlPagingQueryProvider.java @@ -0,0 +1,37 @@ +/* + * 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.database.support; + +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.data.domain.Pageable; + +/** + * HSQLDB implementation of a {@link PagingQueryProvider} using database specific features. + * + * @author Glenn Renfro + */ +public class HsqlPagingQueryProvider extends AbstractSqlPagingQueryProvider { + + @Override + public String getPageQuery(Pageable pageable) { + String topClause = new StringBuilder().append("LIMIT ") + .append(pageable.getOffset()).append(" ") + .append(pageable.getPageSize()).toString(); + return SqlPagingQueryUtils.generateTopJumpToQuery(this, topClause); + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MySqlPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MySqlPagingQueryProvider.java new file mode 100644 index 00000000..2852ae2f --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MySqlPagingQueryProvider.java @@ -0,0 +1,36 @@ +/* + * 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.database.support; + +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.data.domain.Pageable; + +/** + * MySQL implementation of a {@link PagingQueryProvider} using database specific features. + * + * @author Glenn Renfro + */ +public class MySqlPagingQueryProvider extends AbstractSqlPagingQueryProvider { + @Override + public String getPageQuery(Pageable pageable) { + String topClause = new StringBuilder().append("LIMIT ") + .append(pageable.getOffset()).append(", ") + .append(pageable.getPageSize()).toString(); + return SqlPagingQueryUtils.generateLimitJumpToQuery(this, topClause); + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/OraclePagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/OraclePagingQueryProvider.java new file mode 100644 index 00000000..a639f3cf --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/OraclePagingQueryProvider.java @@ -0,0 +1,35 @@ +/* + * 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.database.support; + +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.data.domain.Pageable; + +/** + * Oracle implementation of a {@link PagingQueryProvider} using database specific features. + * + * @author Glenn Renfro + */ +public class OraclePagingQueryProvider extends AbstractSqlPagingQueryProvider { + + @Override + public String getPageQuery(Pageable pageable) { + int offset = pageable.getOffset()+1; + return SqlPagingQueryUtils.generateRowNumSqlQueryWithNesting(this, getSelectClause(), getSelectClause(), false, "TMP_ROW_NUM >= " + + offset + " AND TMP_ROW_NUM < " + (offset+pageable.getPageSize())); + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/PostgresPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/PostgresPagingQueryProvider.java new file mode 100644 index 00000000..18a16c8b --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/PostgresPagingQueryProvider.java @@ -0,0 +1,36 @@ +/* + * 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.database.support; + +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.data.domain.Pageable; + +/** + * Postgres implementation of a {@link PagingQueryProvider} using database specific features. + * + * @author Glenn Renfro + */ +public class PostgresPagingQueryProvider extends AbstractSqlPagingQueryProvider { + + @Override + public String getPageQuery(Pageable pageable) { + String limitClause = new StringBuilder().append("LIMIT "). + append(pageable.getPageSize()).append(" OFFSET "). + append(pageable.getOffset()).toString(); + return SqlPagingQueryUtils.generateLimitJumpToQuery(this, limitClause); + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java new file mode 100644 index 00000000..c18dd89b --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java @@ -0,0 +1,168 @@ +/* + * 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.database.support; + + +import static org.springframework.cloud.task.repository.support.DatabaseType.HSQL; +import static org.springframework.cloud.task.repository.support.DatabaseType.MYSQL; +import static org.springframework.cloud.task.repository.support.DatabaseType.ORACLE; +import static org.springframework.cloud.task.repository.support.DatabaseType.POSTGRES; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.batch.item.database.Order; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.cloud.task.repository.support.DatabaseType; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Factory bean for {@link PagingQueryProvider} interface. The database type + * will be determined from the data source if not provided explicitly. Valid + * types are given by the {@link DatabaseType} enum. + * + * @author Glenn Renfro + */ +public class SqlPagingQueryProviderFactoryBean implements FactoryBean { + + private DataSource dataSource; + + private String databaseType; + + private String fromClause; + + private String whereClause; + + private String selectClause; + + private Map sortKeys; + + private Map providers = new HashMap(); + + + { + providers.put(HSQL, new HsqlPagingQueryProvider()); + providers.put(MYSQL, new MySqlPagingQueryProvider()); + providers.put(POSTGRES, new PostgresPagingQueryProvider()); + providers.put(ORACLE, new OraclePagingQueryProvider()); + } + + /** + * @param databaseType the databaseType to set + */ + public void setDatabaseType(String databaseType) { + Assert.hasText(databaseType, "databaseType must not be empty nor null"); + this.databaseType = databaseType; + } + + /** + * @param dataSource the dataSource to set + */ + public void setDataSource(DataSource dataSource) { + Assert.notNull(dataSource, "dataSource must not be null"); + this.dataSource = dataSource; + } + + /** + * @param fromClause the fromClause to set + */ + public void setFromClause(String fromClause) { + Assert.hasText(fromClause, "fromClause must not be empty nor null"); + this.fromClause = fromClause; + } + + /** + * @param whereClause the whereClause to set + */ + public void setWhereClause(String whereClause) { + this.whereClause = whereClause; + } + + /** + * @param selectClause the selectClause to set + */ + public void setSelectClause(String selectClause) { + Assert.hasText(selectClause, "selectClause must not be empty nor null"); + this.selectClause = selectClause; + } + + /** + * @param sortKeys the sortKeys to set + */ + public void setSortKeys(Map sortKeys) { + this.sortKeys = sortKeys; + } + + /** + * Get a {@link PagingQueryProvider} instance using the provided properties + * and appropriate for the given database type. + * + * @see FactoryBean#getObject() + */ + @Override + public PagingQueryProvider getObject() throws Exception { + + DatabaseType type; + try { + type = databaseType != null ? DatabaseType.valueOf(databaseType.toUpperCase()) : DatabaseType + .fromMetaData(dataSource); + } + catch (MetaDataAccessException e) { + throw new IllegalArgumentException( + "Could not inspect meta data for database type. You have to supply it explicitly.", e); + } + + AbstractSqlPagingQueryProvider provider = providers.get(type); + Assert.state(provider != null, "Should not happen: missing PagingQueryProvider for DatabaseType=" + type); + + provider.setFromClause(fromClause); + provider.setWhereClause(whereClause); + provider.setSortKeys(sortKeys); + if (StringUtils.hasText(selectClause)) { + provider.setSelectClause(selectClause); + } + provider.init(dataSource); + + return provider; + + } + + /** + * Always returns {@link PagingQueryProvider}. + * + * @see FactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return PagingQueryProvider.class; + } + + /** + * Always returns true. + * + * @see FactoryBean#isSingleton() + */ + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java new file mode 100644 index 00000000..7f8acae2 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java @@ -0,0 +1,143 @@ +/* + * 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.database.support; + +import java.util.Map; + +import org.springframework.batch.item.database.Order; + +/** + * Utility class that generates the actual SQL statements used by query + * providers. + * + * @author Glenn Renfro + */ +public class SqlPagingQueryUtils { + + /** + * Generate SQL query string using a LIMIT clause + * + * @param provider {@link AbstractSqlPagingQueryProvider} providing the + * implementation specifics + * @param limitClause the implementation specific top clause to be used + * @return the generated query + */ + public static String generateLimitJumpToQuery(AbstractSqlPagingQueryProvider provider, String limitClause) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(provider.getSelectClause()); + sql.append(" FROM ").append(provider.getFromClause()); + sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); + sql.append(" ORDER BY ").append(buildSortClause(provider)); + sql.append(" " + limitClause); + + return sql.toString(); + } + + /** + * Generate SQL query string using a TOP clause + * + * @param provider {@link AbstractSqlPagingQueryProvider} providing the + * implementation specifics + * @param topClause the implementation specific top clause to be used + * @return the generated query + */ + public static String generateTopJumpToQuery(AbstractSqlPagingQueryProvider provider, String topClause) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(topClause).append(" ").append(provider.getSelectClause()); + sql.append(" FROM ").append(provider.getFromClause()); + sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); + sql.append(" ORDER BY ").append(buildSortClause(provider)); + + return sql.toString(); + } + + public static String generateRowNumSqlQueryWithNesting(AbstractSqlPagingQueryProvider provider, + String selectClause, boolean remainingPageQuery, String rowNumClause) { + return generateRowNumSqlQueryWithNesting(provider, selectClause, selectClause, remainingPageQuery, rowNumClause); + } + + public static String generateRowNumSqlQueryWithNesting(AbstractSqlPagingQueryProvider provider, + String innerSelectClause, String outerSelectClause, boolean remainingPageQuery, String rowNumClause) { + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(outerSelectClause).append(" FROM (SELECT ").append(outerSelectClause) + .append(", ").append("ROWNUM as TMP_ROW_NUM"); + sql.append(" FROM (SELECT ").append(innerSelectClause).append(" FROM ").append(provider.getFromClause()); + buildWhereClause(provider, remainingPageQuery, sql); + sql.append(" ORDER BY ").append(buildSortClause(provider)); + sql.append(")) WHERE ").append(rowNumClause); + + return sql.toString(); + + } + + private static void buildWhereClause(AbstractSqlPagingQueryProvider provider, boolean remainingPageQuery, + StringBuilder sql) { + if (remainingPageQuery) { + sql.append(" WHERE "); + if (provider.getWhereClause() != null) { + sql.append("("); + sql.append(provider.getWhereClause()); + sql.append(") AND "); + } + + } + else { + sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); + } + } + + /** + * Generates ORDER BY attributes based on the sort keys. + * + * @param provider {@link AbstractSqlPagingQueryProvider} providing the + * implementation specifics + * @return a String that can be appended to an ORDER BY clause. + */ + public static String buildSortClause(AbstractSqlPagingQueryProvider provider) { + return buildSortClause(provider.getSortKeys()); + } + + /** + * Generates ORDER BY attributes based on the sort keys. + * + * @param sortKeys generates order by clause from map + * @return a String that can be appended to an ORDER BY clause. + */ + public static String buildSortClause(Map sortKeys) { + StringBuilder builder = new StringBuilder(); + String prefix = ""; + + for (Map.Entry sortKey : sortKeys.entrySet()) { + builder.append(prefix); + + prefix = ", "; + + builder.append(sortKey.getKey()); + + if(sortKey.getValue() != null && sortKey.getValue() == Order.DESCENDING) { + builder.append(" DESC"); + } + else { + builder.append(" ASC"); + } + } + + return builder.toString(); + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorer.java deleted file mode 100644 index ae0a36be..00000000 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorer.java +++ /dev/null @@ -1,52 +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.repository.support; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.springframework.cloud.task.repository.TaskExecution; -import org.springframework.cloud.task.repository.TaskExplorer; - -/** - * Provides a no-op TaskExplorer for development purposes. - * - * @author Michael Minella - */ -public class NoOpTaskExplorer implements TaskExplorer { - - public TaskExecution getTaskExecution(String executionId) { - return null; - } - - public Set findRunningTaskExecutions(String taskName) { - return new HashSet(0); - } - - public List getTaskNames() { - return new ArrayList(0); - } - - public long getTaskExecutionCount(String taskName) { - return 0; - } - - public List getTaskExecutionsByName(String taskName, int start, int count) { - return new ArrayList(0); - } -} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java index 5f7b4529..ec788422 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java @@ -22,6 +22,8 @@ import java.util.Set; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.cloud.task.repository.dao.TaskExecutionDao; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.util.Assert; /** @@ -54,12 +56,23 @@ public class SimpleTaskExplorer implements TaskExplorer{ } @Override - public long getTaskExecutionCount(String taskName) { - return taskExecutionDao.getTaskExecutionCount(taskName); + public long getTaskExecutionCountByTaskName(String taskName) { + return taskExecutionDao.getTaskExecutionCountByTaskName(taskName); + } + + @Override + public long getTaskExecutionCount() { + return taskExecutionDao.getTaskExecutionCount(); } @Override public List getTaskExecutionsByName(String taskName, int start, int count) { return taskExecutionDao.getTaskExecutionsByName(taskName, start, count); } + + @Override + public Page findAll(Pageable pageable) { + return taskExecutionDao.findAll(pageable); + } + } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java index 3d626726..20cf1747 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskRepository.java @@ -29,6 +29,11 @@ import org.springframework.util.Assert; */ public class SimpleTaskRepository implements TaskRepository { + public static final int MAX_EXIT_MESSAGE_SIZE = 2500; + public static final int MAX_TASK_NAME_SIZE = 100; + public static final int MAX_STATUS_CODE_SIZE = 10; + public static final int MAX_EXECUTION_ID_SIZE = 100; + private final static Logger logger = LoggerFactory.getLogger(SimpleTaskRepository.class); private TaskExecutionDao taskExecutionDao; @@ -67,7 +72,27 @@ public class SimpleTaskRepository implements TaskRepository { */ private void validateTaskExecution(TaskExecution taskExecution) { Assert.notNull(taskExecution, "taskExecution should not be null"); - Assert.notNull(taskExecution.getExecutionId(), "taskExecutionId should not be null"); + Assert.hasText(taskExecution.getExecutionId(), "taskExecutionId should not be null"); Assert.notNull(taskExecution.getStartTime(), "TaskExecution start time cannot be null."); + + if (taskExecution.getTaskName() != null && + taskExecution.getTaskName().length() > MAX_TASK_NAME_SIZE) { + throw new IllegalArgumentException("TaskName length exceeds " + + MAX_TASK_NAME_SIZE + " characters"); + } + if (taskExecution.getStatusCode() != null && + taskExecution.getStatusCode().length() > MAX_STATUS_CODE_SIZE) { + throw new IllegalArgumentException("StatusCode length exceeds " + + MAX_STATUS_CODE_SIZE + " characters"); + } + if (taskExecution.getExecutionId().length() > MAX_EXECUTION_ID_SIZE) { + throw new IllegalArgumentException("ExecutionID length exceeds " + + MAX_EXECUTION_ID_SIZE + " characters"); + } + //Trim the exit message + if(taskExecution.getExitMessage() != null && + taskExecution.getExitMessage().length() > MAX_EXIT_MESSAGE_SIZE){ + taskExecution.setExitMessage(taskExecution.getExitMessage().substring(0, MAX_EXIT_MESSAGE_SIZE - 1)); + } } } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java new file mode 100644 index 00000000..5da934bd --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java @@ -0,0 +1,81 @@ +/* + * 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.database.support; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.springframework.cloud.task.util.TestDBUtils; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * @author Glenn Renfro + */ +@RunWith(Parameterized.class) +public class FindAllPagingQueryProviderTests { + + private String databaseProductName; + private String expectedQuery; + private Pageable pageable = new PageRequest(0, 10); + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"Oracle", "SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE FROM " + + "(SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE, ROWNUM as " + + "TMP_ROW_NUM FROM (SELECT TASK_EXECUTION_ID, START_TIME, " + + "END_TIME, TASK_NAME, EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, " + + "STATUS_CODE FROM %PREFIX%EXECUTION ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC)) WHERE TMP_ROW_NUM >= 1 AND " + + "TMP_ROW_NUM < 11"}, + {"HSQL Database Engine","SELECT LIMIT 0 10 TASK_EXECUTION_ID, " + + "START_TIME, END_TIME, TASK_NAME, EXIT_CODE, EXIT_MESSAGE, " + + "LAST_UPDATED, STATUS_CODE FROM %PREFIX%EXECUTION ORDER BY " + + "START_TIME DESC, TASK_EXECUTION_ID DESC"}, + {"PostgreSQL","SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, " + + "TASK_NAME, EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE " + + "FROM %PREFIX%EXECUTION ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC LIMIT 10 OFFSET 0"}, + {"MySQL","SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE FROM " + + "%PREFIX%EXECUTION ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC LIMIT 0, 10"} + }); + } + + public FindAllPagingQueryProviderTests(String databaseProductName, String expectedQuery) { + this.databaseProductName = databaseProductName; + this.expectedQuery = expectedQuery; + } + + @Test + public void testGeneratedQuery() throws Exception{ + String actualQuery = TestDBUtils.getPagingQueryProvider(databaseProductName).getPageQuery(pageable); + assertEquals(String.format( + "the generated query for %s, was not the expected query", + databaseProductName), expectedQuery, actualQuery); + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/InvalidPagingQueryProviderTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/InvalidPagingQueryProviderTests.java new file mode 100644 index 00000000..1c9a9b57 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/InvalidPagingQueryProviderTests.java @@ -0,0 +1,34 @@ +/* + * 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.database.support; + +import org.junit.Test; +import org.springframework.cloud.task.util.TestDBUtils; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * @author Glenn Renfro + */ +public class InvalidPagingQueryProviderTests { + + @Test(expected = IllegalStateException.class) + public void testInvalidDatabase() throws Exception{ + Pageable pageable = new PageRequest(0, 10); + String actualQuery = TestDBUtils.getPagingQueryProvider("Invalid").getPageQuery(pageable); + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBeanTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBeanTests.java new file mode 100644 index 00000000..10bdba06 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBeanTests.java @@ -0,0 +1,65 @@ +/* + * 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.database.support; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Map; +import java.util.TreeMap; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.cloud.task.util.TestDBUtils; + +/** + * @author Glenn Renfro + */ +public class SqlPagingQueryProviderFactoryBeanTests { + + private SqlPagingQueryProviderFactoryBean factoryBean; + + @Before + public void setup() throws Exception{ + factoryBean = new SqlPagingQueryProviderFactoryBean(); + factoryBean.setDataSource(TestDBUtils.getMockDataSource("MySQL")); + factoryBean.setDatabaseType("Oracle"); + factoryBean.setSelectClause(JdbcTaskExecutionDao.SELECT_CLAUSE); + factoryBean.setFromClause(JdbcTaskExecutionDao.FROM_CLAUSE); + Map orderMap = new TreeMap<>(); + orderMap.put("START_TIME", Order.DESCENDING); + orderMap.put("TASK_EXECUTION_ID", Order.DESCENDING); + factoryBean.setSortKeys(orderMap); + + } + + @Test + public void testDatabaseType() throws Exception{ + PagingQueryProvider pagingQueryProvider = factoryBean.getObject(); + assertThat(pagingQueryProvider, instanceOf(OraclePagingQueryProvider.class)); + } + + @Test + public void testIsSingleton(){ + assertTrue(factoryBean.isSingleton()); + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/WhereClausePagingQueryProviderTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/WhereClausePagingQueryProviderTests.java new file mode 100644 index 00000000..d61c9c90 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/WhereClausePagingQueryProviderTests.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.database.support; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.cloud.task.util.TestDBUtils; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * @author Glenn Renfro + */ +@RunWith(Parameterized.class) +public class WhereClausePagingQueryProviderTests { + + private String databaseProductName; + private String expectedQuery; + private Pageable pageable = new PageRequest(0, 10); + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"Oracle", "SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE FROM " + + "(SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE, ROWNUM as " + + "TMP_ROW_NUM FROM (SELECT TASK_EXECUTION_ID, START_TIME, " + + "END_TIME, TASK_NAME, EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, " + + "STATUS_CODE FROM %PREFIX%EXECUTION " + + "WHERE TASK_EXECUTION_ID = '0000' ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC)) WHERE TMP_ROW_NUM >= 1 AND " + + "TMP_ROW_NUM < 11"}, + {"HSQL Database Engine","SELECT LIMIT 0 10 TASK_EXECUTION_ID, " + + "START_TIME, END_TIME, TASK_NAME, EXIT_CODE, EXIT_MESSAGE, " + + "LAST_UPDATED, STATUS_CODE FROM %PREFIX%EXECUTION " + + "WHERE TASK_EXECUTION_ID = '0000' ORDER BY " + + "START_TIME DESC, TASK_EXECUTION_ID DESC"}, + {"PostgreSQL","SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, " + + "TASK_NAME, EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE " + + "FROM %PREFIX%EXECUTION WHERE TASK_EXECUTION_ID = '0000' " + + "ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC LIMIT 10 OFFSET 0"}, + {"MySQL","SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, LAST_UPDATED, STATUS_CODE FROM " + + "%PREFIX%EXECUTION WHERE TASK_EXECUTION_ID = '0000' " + + "ORDER BY START_TIME DESC, " + + "TASK_EXECUTION_ID DESC LIMIT 0, 10"} + }); + } + + public WhereClausePagingQueryProviderTests(String databaseProductName, String expectedQuery) { + this.databaseProductName = databaseProductName; + this.expectedQuery = expectedQuery; + } + + @Test + public void testGeneratedQuery() throws Exception{ + PagingQueryProvider pagingQueryProvider = + TestDBUtils.getPagingQueryProvider(databaseProductName, + "TASK_EXECUTION_ID = '0000'"); + String actualQuery = pagingQueryProvider.getPageQuery(pageable); + assertEquals(String.format( + "the generated query for %s, was not the expected query", + databaseProductName), expectedQuery, actualQuery); + } +} 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 index 388226f9..f2c9627e 100644 --- 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 @@ -19,6 +19,7 @@ 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.MYSQL; 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; @@ -45,6 +46,7 @@ public class DatabaseTypeTests { assertEquals(HSQL, fromProductName("HSQL Database Engine")); assertEquals(ORACLE, fromProductName("Oracle")); assertEquals(POSTGRES, fromProductName("PostgreSQL")); + assertEquals(MYSQL, fromProductName("MySQL")); } @Test(expected = IllegalArgumentException.class) @@ -70,6 +72,12 @@ public class DatabaseTypeTests { assertEquals(POSTGRES, DatabaseType.fromMetaData(ds)); } + @Test + public void testFromMetaDataForMySQL() throws Exception { + DataSource ds = getMockDataSource("MySQL"); + assertEquals(MYSQL, DatabaseType.fromMetaData(ds)); + } + public DataSource getMockDataSource(String databaseProductName) throws Exception { DatabaseMetaData dmd = mock(DatabaseMetaData.class); DataSource ds = mock(DataSource.class); diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorerTests.java deleted file mode 100644 index 77c26b4f..00000000 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/NoOpTaskExplorerTests.java +++ /dev/null @@ -1,59 +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.repository.support; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.cloud.task.repository.TaskExplorer; - -public class NoOpTaskExplorerTests { - - private TaskExplorer taskExplorer; - - @Before - public void setUp() throws Exception { - taskExplorer = new NoOpTaskExplorer(); - } - - @Test - public void testGetTaskExecution() throws Exception { - assertNull(taskExplorer.getTaskExecution("abc")); - } - - @Test - public void testFindRunningTaskExecutions() throws Exception { - assertEquals(taskExplorer.findRunningTaskExecutions("foo").size(), 0); - } - - @Test - public void testGetTaskNames() throws Exception { - assertEquals(taskExplorer.getTaskNames().size(), 0); - } - - @Test - public void testGetTaskExecutionCount() throws Exception { - assertEquals(taskExplorer.getTaskExecutionCount("foo"), 0); - } - - @Test - public void testGetTaskExecutionsByName() throws Exception { - assertEquals(taskExplorer.getTaskExecutionsByName("foo", 0, 100).size(), 0); - } -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java index 47a229a8..995d4edd 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorerTests.java @@ -16,19 +16,23 @@ package org.springframework.cloud.task.repository.support; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.UUID; import javax.sql.DataSource; @@ -40,7 +44,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; @@ -52,6 +55,9 @@ import org.springframework.cloud.task.repository.dao.TaskExecutionDao; import org.springframework.cloud.task.util.TestVerifierUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.io.ResourceLoader; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; /** * @author Glenn Renfro @@ -77,23 +83,24 @@ public class SimpleTaskExplorerTests { @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { - DaoType.jdbc , DaoType.map }); + return Arrays.asList(new Object[]{ + DaoType.jdbc, DaoType.map }); } public SimpleTaskExplorerTests(DaoType testType) { this.testType = testType; } + @Rule public ExpectedException expected = ExpectedException.none(); @Before public void testDefaultContext() throws Exception { - if(testType == DaoType.jdbc){ + if (testType == DaoType.jdbc) { initializeJdbcExplorerTest(); } - else{ + else { initializeMapExplorerTest(); } @@ -109,13 +116,7 @@ public class SimpleTaskExplorerTests { @Test public void getTaskExecution() { - final int TEST_COUNT = 5; - Map expectedResults = new HashMap<>(); - for (int i = 0; i < TEST_COUNT; i++) { - TaskExecution expectedTaskExecution = createAndSaveTaskExecution(); - expectedResults.put(expectedTaskExecution.getExecutionId(), - expectedTaskExecution); - } + Map expectedResults = createSampleDataSet(5); for (String taskExecutionId : expectedResults.keySet()) { TaskExecution actualTaskExecution = taskExplorer.getTaskExecution(taskExecutionId); @@ -129,39 +130,36 @@ public class SimpleTaskExplorerTests { @Test public void taskExecutionNotFound() { - final int TEST_COUNT = 5; - Map expectedResults = new HashMap<>(); - for (int i = 0; i < TEST_COUNT; i++) { - TaskExecution expectedTaskExecution = createAndSaveTaskExecution(); - expectedResults.put(expectedTaskExecution.getExecutionId(), - expectedTaskExecution); - } + Map expectedResults = createSampleDataSet(5); TaskExecution actualTaskExecution = taskExplorer.getTaskExecution("NO_EXECUTION_PRESENT"); - assertNull(String.format( - "expected null for actualTaskExecution %s", testType), - actualTaskExecution); + assertNull(String.format( + "expected null for actualTaskExecution %s", testType), + actualTaskExecution); } @Test public void getTaskCountByTaskName() { - final int TEST_COUNT = 5; - Map expectedResults = new HashMap<>(); - for (int i = 0; i < TEST_COUNT; i++) { - TaskExecution expectedTaskExecution = createAndSaveTaskExecution(); - expectedResults.put(expectedTaskExecution.getExecutionId(), - expectedTaskExecution); - } + Map expectedResults = createSampleDataSet(5); for (Map.Entry entry : expectedResults.entrySet()) { String taskName = entry.getValue().getTaskName(); assertEquals(String.format( "task count for task name did not match expected result for testType %s", testType), - 1, taskExplorer.getTaskExecutionCount(taskName)); + 1, taskExplorer.getTaskExecutionCountByTaskName(taskName)); } } + @Test + public void getTaskCount() { + Map expectedResults = createSampleDataSet(33); + assertEquals(String.format( + "task count did not match expected result for test Type %s", + testType), + 33, taskExplorer.getTaskExecutionCount()); + } + @Test public void findRunningTasks() { final int TEST_COUNT = 2; @@ -227,7 +225,7 @@ public class SimpleTaskExplorerTests { expectedResults.containsKey(result.getExecutionId())); assertEquals( String.format("taskName for taskExecution is incorrect for testType %s", - testType), TASK_NAME, result.getTaskName()); + testType), TASK_NAME, result.getTaskName()); } } @@ -246,13 +244,83 @@ public class SimpleTaskExplorerTests { } } + @Test + public void findAllExecutionsOffBoundry() { + Pageable pageable = new PageRequest(0, 10); + verifyPageResults(pageable, 103); + } + + @Test + public void findAllExecutionsOffBoundryByOne() { + Pageable pageable = new PageRequest(0, 10); + verifyPageResults(pageable, 101); + } + + @Test + public void findAllExecutionsOnBoundry() { + Pageable pageable = new PageRequest(0, 10); + verifyPageResults(pageable, 100); + } + + @Test + public void findAllExecutionsNoResult() { + Pageable pageable = new PageRequest(0, 10); + verifyPageResults(pageable, 0); + } + + private void verifyPageResults(Pageable pageable, int totalNumberOfExecs) { + Map expectedResults = createSampleDataSet(totalNumberOfExecs); + List sortedExecIds = getSortedOfTaskExecIds(expectedResults); + Iterator expectedTaskExecutionIter = sortedExecIds.iterator(); + //Verify pageable totals + Page taskPage = taskExplorer.findAll(pageable); + int pagesExpected = (int) Math.ceil(totalNumberOfExecs / ((double) pageable.getPageSize())); + assertEquals("actual page count return was not the expected total", + pagesExpected, + taskPage.getTotalPages()); + assertEquals("actual element count was not the expected count", totalNumberOfExecs, + taskPage.getTotalElements()); + + //Verify pagination + Pageable actualPageable = new PageRequest(0, pageable.getPageSize()); + boolean hasMorePages = taskPage.hasContent(); + int pageNumber = 0; + int elementCount = 0; + while (hasMorePages) { + taskPage = taskExplorer.findAll(actualPageable); + hasMorePages = taskPage.hasNext(); + List actualTaskExecutions = taskPage.getContent(); + int expectedPageSize = pageable.getPageSize(); + if (!hasMorePages && pageable.getPageSize() != actualTaskExecutions.size()) { + expectedPageSize = totalNumberOfExecs % pageable.getPageSize(); + } + assertEquals( + String.format("Element count on page did not match on the %n page", + pageNumber), expectedPageSize, actualTaskExecutions.size()); + for (TaskExecution actualExecution : actualTaskExecutions) { + assertEquals(String.format("Element on page %n did not match expected", + pageNumber), expectedTaskExecutionIter.next(), + actualExecution.getExecutionId()); + TestVerifierUtils.verifyTaskExecution( + expectedResults.get(actualExecution.getExecutionId()), + actualExecution); + elementCount++; + } + actualPageable = taskPage.nextPageable(); + pageNumber++; + } + //Verify actual totals + assertEquals("Pages processed did not equal expected", pagesExpected, pageNumber); + assertEquals("Elements processed did not equal expected,", totalNumberOfExecs, elementCount); + } + private TaskExecution createAndSaveTaskExecution() { TaskExecution taskExecution = TestVerifierUtils.createSampleTaskExecution(); dao.saveTaskExecution(taskExecution); return taskExecution; } - private void initializeJdbcExplorerTest(){ + private void initializeJdbcExplorerTest() { this.context = new AnnotationConfigApplicationContext(); this.context.register(TestConfiguration.class, EmbeddedDataSourceConfiguration.class, @@ -273,6 +341,39 @@ public class SimpleTaskExplorerTests { AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); } + private Map createSampleDataSet(int count){ + Map expectedResults = new HashMap<>(); + for (int i = 0; i < count; i++) { + TaskExecution expectedTaskExecution = createAndSaveTaskExecution(); + expectedResults.put(expectedTaskExecution.getExecutionId(), + expectedTaskExecution); + } + return expectedResults; + } + + private List getSortedOfTaskExecIds(Map taskExecutionMap){ + List sortedExecIds = new ArrayList<>(taskExecutionMap.size()); + TreeSet sortedSet = getTreeSet(); + sortedSet.addAll(taskExecutionMap.values()); + Iterator iterator = sortedSet.descendingIterator(); + while(iterator.hasNext()){ + sortedExecIds.add(iterator.next().getExecutionId()); + } + return sortedExecIds; + } + + private TreeSet getTreeSet(){ + return new TreeSet(new Comparator() { + @Override + public int compare(TaskExecution e1, TaskExecution e2) { + int result = e1.getStartTime().compareTo(e2.getStartTime()); + if (result == 0){ + result = e1.getExecutionId().compareTo(e2.getExecutionId()); + } + return result; + } + }); + } private enum DaoType{jdbc, map} } 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 index 49216580..24182fff 100644 --- 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 @@ -93,5 +93,33 @@ public class SimpleTaskRepositoryJdbcTests { dataSource, expectedTaskExecution.getExecutionId()); TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution); } + + @Test + public void testCreateTaskExecutionNoParamMaxExitMessageSize(){ + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + expectedTaskExecution.setExitMessage(new String(new char[SimpleTaskRepository.MAX_EXIT_MESSAGE_SIZE+1])); + taskRepository.createTaskExecution(expectedTaskExecution); + } + + @Test(expected=IllegalArgumentException.class) + public void testCreateTaskExecutionNoParamMaxTaskName(){ + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + expectedTaskExecution.setTaskName(new String(new char[SimpleTaskRepository.MAX_TASK_NAME_SIZE+1])); + taskRepository.createTaskExecution(expectedTaskExecution); + } + + @Test(expected=IllegalArgumentException.class) + public void testCreateTaskExecutionNoParamMaxStatus(){ + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + expectedTaskExecution.setStatusCode(new String(new char[SimpleTaskRepository.MAX_STATUS_CODE_SIZE+1])); + taskRepository.createTaskExecution(expectedTaskExecution); + } + + @Test(expected=IllegalArgumentException.class) + public void testCreateTaskExecutionNoParamMaxExecutionId(){ + TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam(); + expectedTaskExecution.setExecutionId(new String(new char[SimpleTaskRepository.MAX_EXECUTION_ID_SIZE+1])); + taskRepository.createTaskExecution(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 index 51eced62..010e1d63 100644 --- 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 @@ -17,16 +17,25 @@ package org.springframework.cloud.task.util; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.TreeMap; import javax.sql.DataSource; +import org.springframework.batch.item.database.Order; import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.cloud.task.repository.database.PagingQueryProvider; +import org.springframework.cloud.task.repository.database.support.SqlPagingQueryProviderFactoryBean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -39,10 +48,10 @@ import org.springframework.jdbc.core.RowMapper; public class TestDBUtils { /** - * Retrieves the TaskExecution from the datasource + * 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 . + * @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, @@ -73,6 +82,58 @@ public class TestDBUtils { return taskExecution; } + /** + * Create a pagingQueryProvider specific database type with a findAll. + * @param databaseProductName of the database. + * @return a PagingQueryPovider that will return all the requested information. + * @throws Exception + */ + public static PagingQueryProvider getPagingQueryProvider(String databaseProductName) throws Exception{ + return getPagingQueryProvider(databaseProductName, null); + } + + /** + * Create a pagingQueryProvider specific database type with a query containing a where clause. + * @param databaseProductName of the database. + * @param whereClause to be applied to the query. + * @return a PagingQueryProvider that will return the requested information. + * @throws Exception + */ + public static PagingQueryProvider getPagingQueryProvider(String databaseProductName, + String whereClause) throws Exception{ + DataSource dataSource = getMockDataSource(databaseProductName); + Map orderMap = new TreeMap<>(); + orderMap.put("START_TIME", Order.DESCENDING); + orderMap.put("TASK_EXECUTION_ID", Order.DESCENDING); + SqlPagingQueryProviderFactoryBean factoryBean = new SqlPagingQueryProviderFactoryBean(); + factoryBean.setSelectClause(JdbcTaskExecutionDao.SELECT_CLAUSE); + factoryBean.setFromClause(JdbcTaskExecutionDao.FROM_CLAUSE); + if(whereClause != null){ + factoryBean.setWhereClause(whereClause); + } + factoryBean.setSortKeys(orderMap); + factoryBean.setDataSource(dataSource); + PagingQueryProvider pagingQueryProvider = null; + try { + pagingQueryProvider = factoryBean.getObject(); + pagingQueryProvider.init(dataSource); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + return pagingQueryProvider; + } + + public static 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; + } + private static void populateParamsToDB(DataSource dataSource, TaskExecution taskExecution) { String sql = "SELECT * FROM TASK_EXECUTION_PARAMS WHERE TASK_EXECUTION_ID = '" + taskExecution.getExecutionId() + "'";