SCT-41 Introduces findAll for task explorer
resolves spring-cloud/spring-cloud-task#41
This commit is contained in:
committed by
Michael Minella
parent
f30cbf36bf
commit
0e90778f20
@@ -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<TaskExecution> 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<TaskExecution> findAll(Pageable pageable);
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Order> 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<TaskExecution> 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<TaskExecution> 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<TaskExecution> resultList = jdbcTemplate.query(
|
||||
getQuery(query),
|
||||
new Object[]{ },
|
||||
new TaskExecutionRowMapper());
|
||||
return new PageImpl<TaskExecution>(resultList, pageable, getTaskExecutionCount());
|
||||
}
|
||||
|
||||
private String getQuery(String base) {
|
||||
return StringUtils.replace(base, "%PREFIX%", tablePrefix);
|
||||
}
|
||||
|
||||
@@ -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<String, TaskExecution> 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<TaskExecution> findRunningTaskExecutions(String taskName) {
|
||||
Set<TaskExecution> result = getTaskExecutionTreeSet();
|
||||
@@ -101,6 +109,19 @@ public class MapTaskExecutionDao implements TaskExecutionDao {
|
||||
return new ArrayList<String>(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TaskExecution> findAll(Pageable pageable) {
|
||||
TreeSet<TaskExecution> sortedSet = getTaskExecutionTreeSet();
|
||||
sortedSet.addAll(taskExecutions.values());
|
||||
List<TaskExecution> result = new ArrayList<>(sortedSet.descendingSet());
|
||||
int toIndex = (pageable.getOffset() + pageable.getPageSize() > result.size()) ?
|
||||
result.size() : pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl<TaskExecution>(
|
||||
result.subList(pageable.getOffset(), toIndex),
|
||||
pageable,
|
||||
getTaskExecutionCount());
|
||||
}
|
||||
|
||||
public Map<String, TaskExecution> getTaskExecutions() {
|
||||
return Collections.unmodifiableMap(taskExecutions);
|
||||
}
|
||||
@@ -109,7 +130,11 @@ public class MapTaskExecutionDao implements TaskExecutionDao {
|
||||
return new TreeSet<TaskExecution>(new Comparator<TaskExecution>() {
|
||||
@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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<String> 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<TaskExecution> findAll(Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -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<String, Order> 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);
|
||||
}
|
||||
@@ -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<String, Order> sortKeys = new LinkedHashMap<String, Order>();
|
||||
|
||||
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<String, Order> 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<String, Order> 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<String> namedParameters = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<PagingQueryProvider> {
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private String databaseType;
|
||||
|
||||
private String fromClause;
|
||||
|
||||
private String whereClause;
|
||||
|
||||
private String selectClause;
|
||||
|
||||
private Map<String, Order> sortKeys;
|
||||
|
||||
private Map<DatabaseType, AbstractSqlPagingQueryProvider> providers = new HashMap<DatabaseType, AbstractSqlPagingQueryProvider>();
|
||||
|
||||
|
||||
{
|
||||
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<String, Order> 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<PagingQueryProvider> getObjectType() {
|
||||
return PagingQueryProvider.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true.
|
||||
*
|
||||
* @see FactoryBean#isSingleton()
|
||||
*/
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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<String, Order> sortKeys) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String prefix = "";
|
||||
|
||||
for (Map.Entry<String, Order> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<TaskExecution> findRunningTaskExecutions(String taskName) {
|
||||
return new HashSet<TaskExecution>(0);
|
||||
}
|
||||
|
||||
public List<String> getTaskNames() {
|
||||
return new ArrayList<String>(0);
|
||||
}
|
||||
|
||||
public long getTaskExecutionCount(String taskName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List<TaskExecution> getTaskExecutionsByName(String taskName, int start, int count) {
|
||||
return new ArrayList<TaskExecution>(0);
|
||||
}
|
||||
}
|
||||
@@ -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<TaskExecution> getTaskExecutionsByName(String taskName, int start, int count) {
|
||||
return taskExecutionDao.getTaskExecutionsByName(taskName, start, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TaskExecution> findAll(Pageable pageable) {
|
||||
return taskExecutionDao.findAll(pageable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user