/* * Copyright 2002-2016 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.integration.jdbc; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.core.MessageSource; import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapperResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.messaging.Message; /** * A polling channel adapter that creates messages from the payload returned by * executing a select query. Optionally an update can be executed after the * select in order to update processed rows. * * @author Jonas Partner * @author Dave Syer * @author Artem Bilan * * @since 2.0 */ public class JdbcPollingChannelAdapter extends IntegrationObjectSupport implements MessageSource<Object> { private final NamedParameterJdbcOperations jdbcOperations; private final String selectQuery; private volatile RowMapper<?> rowMapper; private volatile SqlParameterSource sqlQueryParameterSource; private volatile boolean updatePerRow = false; private volatile String updateSql; private volatile SqlParameterSourceFactory sqlParameterSourceFactory = new ExpressionEvaluatingSqlParameterSourceFactory(); private volatile boolean sqlParameterSourceFactorySet; private volatile int maxRowsPerPoll = 0; /** * Constructor taking {@link DataSource} from which the DB Connection can be * obtained and the select query to execute to retrieve new rows. * * @param dataSource Must not be null * @param selectQuery query to execute */ public JdbcPollingChannelAdapter(DataSource dataSource, String selectQuery) { this.jdbcOperations = new NamedParameterJdbcTemplate(dataSource); this.selectQuery = selectQuery; } /** * Constructor taking {@link JdbcOperations} instance to use for query * execution and the select query to execute to retrieve new rows. * * @param jdbcOperations instance to use for query execution * @param selectQuery query to execute */ public JdbcPollingChannelAdapter(JdbcOperations jdbcOperations, String selectQuery) { this.jdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations); this.selectQuery = selectQuery; } public void setRowMapper(RowMapper<?> rowMapper) { this.rowMapper = rowMapper; } public void setUpdateSql(String updateSql) { this.updateSql = updateSql; } public void setUpdatePerRow(boolean updatePerRow) { this.updatePerRow = updatePerRow; } public void setUpdateSqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) { this.sqlParameterSourceFactory = sqlParameterSourceFactory; this.sqlParameterSourceFactorySet = true; } /** * A source of parameters for the select query used for polling. * * @param sqlQueryParameterSource the sql query parameter source to set */ public void setSelectSqlParameterSource(SqlParameterSource sqlQueryParameterSource) { this.sqlQueryParameterSource = sqlQueryParameterSource; } /** * The maximum number of rows to pull out of the query results per poll (if * greater than zero, otherwise all rows will be packed into the outgoing * message). Default is zero. * * @param maxRows the max rows to set */ public void setMaxRowsPerPoll(int maxRows) { this.maxRowsPerPoll = maxRows; } @Override protected void onInit() throws Exception { super.onInit(); if (!this.sqlParameterSourceFactorySet && this.getBeanFactory() != null) { ((ExpressionEvaluatingSqlParameterSourceFactory) this.sqlParameterSourceFactory) .setBeanFactory(this.getBeanFactory()); } } /** * Execute the query. If a query result set contains one or more rows, the * Message payload will contain either a List of Maps for each row or, if a * RowMapper has been provided, the values mapped from those rows. If the * query returns no rows, this method will return <code>null</code>. * #return the {@link Message} or {@code null} as a result of query. */ @Override public Message<Object> receive() { Object payload = poll(); if (payload == null) { return null; } return this.getMessageBuilderFactory().withPayload(payload).build(); } /** * Execute the select query and the update query if provided. Returns the * rows returned by the select query. If a RowMapper has been provided, the * mapped results are returned. */ private Object poll() { List<?> payload = doPoll(this.sqlQueryParameterSource); if (payload.size() < 1) { payload = null; } if (payload != null && this.updateSql != null) { if (this.updatePerRow) { for (Object row : payload) { executeUpdateQuery(row); } } else { executeUpdateQuery(payload); } } return payload; } private void executeUpdateQuery(Object obj) { SqlParameterSource updateParameterSource = this.sqlParameterSourceFactory.createParameterSource(obj); this.jdbcOperations.update(this.updateSql, updateParameterSource); } protected List<?> doPoll(SqlParameterSource sqlQueryParameterSource) { final RowMapper<?> rowMapper = this.rowMapper == null ? new ColumnMapRowMapper() : this.rowMapper; ResultSetExtractor<List<Object>> resultSetExtractor; if (this.maxRowsPerPoll > 0) { resultSetExtractor = rs -> { List<Object> results = new ArrayList<Object>(JdbcPollingChannelAdapter.this.maxRowsPerPoll); int rowNum = 0; while (rs.next() && rowNum < JdbcPollingChannelAdapter.this.maxRowsPerPoll) { results.add(rowMapper.mapRow(rs, rowNum++)); } return results; }; } else { @SuppressWarnings("unchecked") ResultSetExtractor<List<Object>> temp = new RowMapperResultSetExtractor<Object>((RowMapper<Object>) rowMapper); resultSetExtractor = temp; } if (sqlQueryParameterSource != null) { return this.jdbcOperations.query(this.selectQuery, sqlQueryParameterSource, resultSetExtractor); } else { return this.jdbcOperations.getJdbcOperations().query(this.selectQuery, resultSetExtractor); } } @Override public String getComponentType() { return "jdbc:inbound-channel-adapter"; } }