/* * Copyright 2006-2013 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.batch.admin.service; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao; import org.springframework.batch.item.database.Order; import org.springframework.batch.item.database.PagingQueryProvider; import org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean; import org.springframework.batch.support.PatternMatcher; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.incrementer.AbstractDataFieldMaxValueIncrementer; import org.springframework.util.Assert; /** * @author Dave Syer * @author Michael Minella * */ public class JdbcSearchableStepExecutionDao extends JdbcStepExecutionDao implements SearchableStepExecutionDao { private static final String STEP_EXECUTIONS_FOR_JOB = "SELECT distinct STEP_NAME from %PREFIX%STEP_EXECUTION S, %PREFIX%JOB_EXECUTION E, %PREFIX%JOB_INSTANCE I " + "where S.JOB_EXECUTION_ID = E.JOB_EXECUTION_ID AND E.JOB_INSTANCE_ID = I.JOB_INSTANCE_ID AND I.JOB_NAME = ?"; private static final String COUNT_STEP_EXECUTIONS_FOR_STEP = "SELECT COUNT(STEP_EXECUTION_ID) from %PREFIX%STEP_EXECUTION S, %PREFIX%JOB_EXECUTION E, %PREFIX%JOB_INSTANCE I " + "where S.JOB_EXECUTION_ID = E.JOB_EXECUTION_ID AND E.JOB_INSTANCE_ID = I.JOB_INSTANCE_ID AND I.JOB_NAME = ? AND S.STEP_NAME = ?"; private static final String COUNT_STEP_EXECUTIONS_FOR_STEP_PATTERN = "SELECT COUNT(STEP_EXECUTION_ID) from %PREFIX%STEP_EXECUTION S, %PREFIX%JOB_EXECUTION E, %PREFIX%JOB_INSTANCE I" + " where S.JOB_EXECUTION_ID = E.JOB_EXECUTION_ID AND E.JOB_INSTANCE_ID = I.JOB_INSTANCE_ID AND I.JOB_NAME = ? AND S.STEP_NAME like ?"; private static final String FIELDS = "S.STEP_EXECUTION_ID, S.STEP_NAME, S.START_TIME, S.END_TIME, S.STATUS, S.COMMIT_COUNT," + " S.READ_COUNT, S.FILTER_COUNT, S.WRITE_COUNT, S.EXIT_CODE, S.EXIT_MESSAGE, S.READ_SKIP_COUNT, S.WRITE_SKIP_COUNT," + " S.PROCESS_SKIP_COUNT, S.ROLLBACK_COUNT, S.LAST_UPDATED, S.VERSION"; private DataSource dataSource; /** * @param dataSource the dataSource to set */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * @see JdbcJobExecutionDao#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { Assert.state(dataSource != null, "DataSource must be provided"); if (getJdbcTemplate() == null) { setJdbcTemplate(new JdbcTemplate(dataSource)); } setStepExecutionIncrementer(new AbstractDataFieldMaxValueIncrementer() { @Override protected long getNextKey() { return 0; } }); super.afterPropertiesSet(); } public Collection<String> findStepNamesForJobExecution(String jobName, String excludesPattern) { List<String> list = getJdbcTemplate().query(getQuery(STEP_EXECUTIONS_FOR_JOB), new RowMapper<String>() { public String mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { return rs.getString(1); } }, jobName); Set<String> stepNames = new LinkedHashSet<String>(list); for (Iterator<String> iterator = stepNames.iterator(); iterator.hasNext();) { String name = iterator.next(); if (PatternMatcher.match(excludesPattern, name)) { iterator.remove(); } } return stepNames; } public Collection<StepExecution> findStepExecutions(String jobName, String stepName, int start, int count) { String whereClause; if (jobName.contains("*")) { whereClause = "JOB_NAME like ?"; jobName = jobName.replace("*", "%"); } else { whereClause = "JOB_NAME = ?"; } if (stepName.contains("*")) { whereClause = whereClause + " AND STEP_NAME like ?"; stepName = stepName.replace("*", "%"); } else { whereClause = whereClause + " AND STEP_NAME = ?"; } PagingQueryProvider queryProvider = getPagingQueryProvider(whereClause); List<StepExecution> stepExecutions; if (start <= 0) { stepExecutions = getJdbcTemplate().query(queryProvider.generateFirstPageQuery(count), new StepExecutionRowMapper(), jobName, stepName); } else { try { Long startAfterValue = getJdbcTemplate().queryForObject( queryProvider.generateJumpToItemQuery(start, count), Long.class, jobName, stepName); stepExecutions = getJdbcTemplate().query(queryProvider.generateRemainingPagesQuery(count), new StepExecutionRowMapper(), jobName, stepName, startAfterValue); } catch (IncorrectResultSizeDataAccessException e) { return Collections.emptyList(); } } return stepExecutions; } public int countStepExecutions(String jobName, String stepName) { if (stepName.contains("*")) { return getJdbcTemplate().queryForObject(getQuery(COUNT_STEP_EXECUTIONS_FOR_STEP_PATTERN), Integer.class, jobName, stepName.replace("*", "%")); } return getJdbcTemplate().queryForObject(getQuery(COUNT_STEP_EXECUTIONS_FOR_STEP), Integer.class, jobName, stepName); } /** * @return a {@link PagingQueryProvider} with a where clause to narrow the * query * @throws Exception */ private PagingQueryProvider getPagingQueryProvider(String whereClause) { SqlPagingQueryProviderFactoryBean factory = new SqlPagingQueryProviderFactoryBean(); factory.setDataSource(dataSource); factory.setFromClause(getQuery("%PREFIX%STEP_EXECUTION S, %PREFIX%JOB_EXECUTION J, %PREFIX%JOB_INSTANCE I")); factory.setSelectClause(FIELDS); Map<String, Order> sortKeys = new HashMap<String, Order>(); sortKeys.put("STEP_EXECUTION_ID", Order.DESCENDING); factory.setSortKeys(sortKeys); if (whereClause != null) { factory.setWhereClause(whereClause + " AND S.JOB_EXECUTION_ID = J.JOB_EXECUTION_ID AND J.JOB_INSTANCE_ID = I.JOB_INSTANCE_ID"); } try { return (PagingQueryProvider) factory.getObject(); } catch (Exception e) { throw new IllegalStateException("Unexpected exception creating paging query provide", e); } } private static class StepExecutionRowMapper implements RowMapper<StepExecution> { public StepExecution mapRow(ResultSet rs, int rowNum) throws SQLException { StepExecution stepExecution = new StepExecution(rs.getString(2), null); stepExecution.setId(rs.getLong(1)); stepExecution.setStartTime(rs.getTimestamp(3)); stepExecution.setEndTime(rs.getTimestamp(4)); stepExecution.setStatus(BatchStatus.valueOf(rs.getString(5))); stepExecution.setCommitCount(rs.getInt(6)); stepExecution.setReadCount(rs.getInt(7)); stepExecution.setFilterCount(rs.getInt(8)); stepExecution.setWriteCount(rs.getInt(9)); stepExecution.setExitStatus(new ExitStatus(rs.getString(10), rs.getString(11))); stepExecution.setReadSkipCount(rs.getInt(12)); stepExecution.setWriteSkipCount(rs.getInt(13)); stepExecution.setProcessSkipCount(rs.getInt(14)); stepExecution.setRollbackCount(rs.getInt(15)); stepExecution.setLastUpdated(rs.getTimestamp(16)); stepExecution.setVersion(rs.getInt(17)); return stepExecution; } } }