001/*
002 * Copyright 2016-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.item.database.builder;
017
018import java.util.List;
019import javax.sql.DataSource;
020
021import org.springframework.batch.item.database.AbstractCursorItemReader;
022import org.springframework.batch.item.database.JdbcCursorItemReader;
023import org.springframework.batch.item.database.support.ListPreparedStatementSetter;
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
026import org.springframework.jdbc.core.ArgumentTypePreparedStatementSetter;
027import org.springframework.jdbc.core.BeanPropertyRowMapper;
028import org.springframework.jdbc.core.PreparedStatementSetter;
029import org.springframework.jdbc.core.RowMapper;
030import org.springframework.util.Assert;
031import org.springframework.util.StringUtils;
032
033/**
034 * Builder for the {@link JdbcCursorItemReader}
035 *
036 * @author Michael Minella
037 * @author Glenn Renfro
038 * @since 4.0
039 */
040public class JdbcCursorItemReaderBuilder<T> {
041
042        private DataSource dataSource;
043
044        private int fetchSize = AbstractCursorItemReader.VALUE_NOT_SET;
045
046        private int maxRows = AbstractCursorItemReader.VALUE_NOT_SET;
047
048        private int queryTimeout = AbstractCursorItemReader.VALUE_NOT_SET;
049
050        private boolean ignoreWarnings;
051
052        private boolean verifyCursorPosition;
053
054        private boolean driverSupportsAbsolute;
055
056        private boolean useSharedExtendedConnection;
057
058        private PreparedStatementSetter preparedStatementSetter;
059
060        private String sql;
061
062        private RowMapper<T> rowMapper;
063
064        private boolean saveState = true;
065
066        private String name;
067
068        private int maxItemCount = Integer.MAX_VALUE;
069
070        private int currentItemCount;
071
072        /**
073         * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport}
074         * should be persisted within the {@link org.springframework.batch.item.ExecutionContext}
075         * for restart purposes.
076         *
077         * @param saveState defaults to true
078         * @return The current instance of the builder.
079         */
080        public JdbcCursorItemReaderBuilder<T> saveState(boolean saveState) {
081                this.saveState = saveState;
082
083                return this;
084        }
085
086        /**
087         * The name used to calculate the key within the
088         * {@link org.springframework.batch.item.ExecutionContext}. Required if
089         * {@link #saveState(boolean)} is set to true.
090         *
091         * @param name name of the reader instance
092         * @return The current instance of the builder.
093         * @see org.springframework.batch.item.ItemStreamSupport#setName(String)
094         */
095        public JdbcCursorItemReaderBuilder<T> name(String name) {
096                this.name = name;
097
098                return this;
099        }
100
101        /**
102         * Configure the max number of items to be read.
103         *
104         * @param maxItemCount the max items to be read
105         * @return The current instance of the builder.
106         * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int)
107         */
108        public JdbcCursorItemReaderBuilder<T> maxItemCount(int maxItemCount) {
109                this.maxItemCount = maxItemCount;
110
111                return this;
112        }
113
114        /**
115         * Index for the current item. Used on restarts to indicate where to start from.
116         *
117         * @param currentItemCount current index
118         * @return this instance for method chaining
119         * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int)
120         */
121        public JdbcCursorItemReaderBuilder<T> currentItemCount(int currentItemCount) {
122                this.currentItemCount = currentItemCount;
123
124                return this;
125        }
126
127        /**
128         * The {@link DataSource} to read from
129         *
130         * @param dataSource a relational data base
131         * @return this instance for method chaining
132         * @see JdbcCursorItemReader#setDataSource(DataSource)
133         */
134        public JdbcCursorItemReaderBuilder<T> dataSource(DataSource dataSource) {
135                this.dataSource = dataSource;
136
137                return this;
138        }
139
140        /**
141         * A hint to the driver as to how many rows to return with each fetch.
142         *
143         * @param fetchSize the hint
144         * @return this instance for method chaining
145         * @see JdbcCursorItemReader#setFetchSize(int)
146         */
147        public JdbcCursorItemReaderBuilder<T> fetchSize(int fetchSize) {
148                this.fetchSize = fetchSize;
149
150                return this;
151        }
152
153        /**
154         * The max number of rows the {@link java.sql.ResultSet} can contain
155         *
156         * @param maxRows the max
157         * @return this instance for method chaining
158         * @see JdbcCursorItemReader#setMaxRows(int)
159         */
160        public JdbcCursorItemReaderBuilder<T> maxRows(int maxRows) {
161                this.maxRows = maxRows;
162
163                return this;
164        }
165
166        /**
167         * The time in milliseconds for the query to timeout
168         *
169         * @param queryTimeout timeout
170         * @return this instance for method chaining
171         * @see JdbcCursorItemReader#setQueryTimeout(int)
172         */
173        public JdbcCursorItemReaderBuilder<T> queryTimeout(int queryTimeout) {
174                this.queryTimeout = queryTimeout;
175
176                return this;
177        }
178
179        public JdbcCursorItemReaderBuilder<T> ignoreWarnings(boolean ignoreWarnings) {
180                this.ignoreWarnings = ignoreWarnings;
181
182                return this;
183        }
184
185        /**
186         * Indicates if the reader should verify the current position of the
187         * {@link java.sql.ResultSet} after being passed to the {@link RowMapper}.  Defaults
188         * to true.
189         *
190         * @param verifyCursorPosition indicator
191         * @return this instance for method chaining
192         * @see JdbcCursorItemReader#setVerifyCursorPosition(boolean)
193         */
194        public JdbcCursorItemReaderBuilder<T> verifyCursorPosition(boolean verifyCursorPosition) {
195                this.verifyCursorPosition = verifyCursorPosition;
196
197                return this;
198        }
199
200        /**
201         * Indicates if the JDBC driver supports setting the absolute row on the
202         * {@link java.sql.ResultSet}.
203         *
204         * @param driverSupportsAbsolute indicator
205         * @return this instance for method chaining
206         * @see JdbcCursorItemReader#setDriverSupportsAbsolute(boolean)
207         */
208        public JdbcCursorItemReaderBuilder<T> driverSupportsAbsolute(boolean driverSupportsAbsolute) {
209                this.driverSupportsAbsolute = driverSupportsAbsolute;
210
211                return this;
212        }
213
214        /**
215         * Indicates that the connection used for the cursor is being used by all other
216         * processing, therefor part of the same transaction.
217         *
218         * @param useSharedExtendedConnection indicator
219         * @return this instance for method chaining
220         * @see JdbcCursorItemReader#setUseSharedExtendedConnection(boolean)
221         */
222        public JdbcCursorItemReaderBuilder<T> useSharedExtendedConnection(boolean useSharedExtendedConnection) {
223                this.useSharedExtendedConnection = useSharedExtendedConnection;
224
225                return this;
226        }
227
228        /**
229         * Configures the provided {@link PreparedStatementSetter} to be used to populate any
230         * arguments in the SQL query to be executed for the reader.
231         *
232         * @param preparedStatementSetter setter
233         * @return this instance for method chaining
234         * @see JdbcCursorItemReader#setPreparedStatementSetter(PreparedStatementSetter)
235         */
236        public JdbcCursorItemReaderBuilder<T> preparedStatementSetter(PreparedStatementSetter preparedStatementSetter) {
237                this.preparedStatementSetter = preparedStatementSetter;
238
239                return this;
240        }
241
242        /**
243         * Configures a {@link PreparedStatementSetter} that will use the array as the values
244         * to be set on the query to be executed for this reader.
245         *
246         * @param args values to set on the reader query
247         * @return this instance for method chaining
248         */
249        public JdbcCursorItemReaderBuilder<T> queryArguments(Object[] args) {
250                this.preparedStatementSetter = new ArgumentPreparedStatementSetter(args);
251
252                return this;
253        }
254
255        /**
256         * Configures a {@link PreparedStatementSetter} that will use the Object [] as the
257         * values to be set on the query to be executed for this reader.  The int[] will
258         * provide the types ({@link java.sql.Types}) for each of the values provided.
259         *
260         * @param args values to set on the query
261         * @param types the type for each value in the args array
262         * @return this instance for method chaining
263         */
264        public JdbcCursorItemReaderBuilder<T> queryArguments(Object[] args, int[] types) {
265                this.preparedStatementSetter = new ArgumentTypePreparedStatementSetter(args, types);
266
267                return this;
268        }
269
270        /**
271         * Configures a {@link PreparedStatementSetter} that will use the List as the values
272         * to be set on the query to be executed for this reader.
273         *
274         * @param args values to set on the query
275         * @return this instance for method chaining
276         * @throws Exception from {@link InitializingBean#afterPropertiesSet()}
277         */
278        public JdbcCursorItemReaderBuilder<T> queryArguments(List<?> args) throws Exception {
279                ListPreparedStatementSetter listPreparedStatementSetter = new ListPreparedStatementSetter(args);
280
281                listPreparedStatementSetter.afterPropertiesSet();
282
283                this.preparedStatementSetter = listPreparedStatementSetter;
284
285                return this;
286        }
287
288        /**
289         * The query to be executed for this reader
290         *
291         * @param sql query
292         * @return this instance for method chaining
293         * @see JdbcCursorItemReader#setSql(String)
294         */
295        public JdbcCursorItemReaderBuilder<T> sql(String sql) {
296                this.sql = sql;
297
298                return this;
299        }
300
301        /**
302         * The {@link RowMapper} used to map the results of the cursor to each item.
303         *
304         * @param rowMapper {@link RowMapper}
305         * @return this instance for method chaining
306         * @see JdbcCursorItemReader#setRowMapper(RowMapper)
307         */
308        public JdbcCursorItemReaderBuilder<T> rowMapper(RowMapper<T> rowMapper) {
309                this.rowMapper = rowMapper;
310
311                return this;
312        }
313
314        /**
315         * Creates a {@link BeanPropertyRowMapper} to be used as your
316         * {@link RowMapper}.
317         *
318         * @param mappedClass the class for the row mapper
319         * @return this instance for method chaining
320         * @see BeanPropertyRowMapper
321         */
322        public JdbcCursorItemReaderBuilder<T> beanRowMapper(Class<T> mappedClass) {
323                this.rowMapper = new BeanPropertyRowMapper<>(mappedClass);
324
325                return this;
326        }
327
328        /**
329         * Validates configuration and builds a new reader instance.
330         *
331         * @return a fully constructed {@link JdbcCursorItemReader}
332         */
333        public JdbcCursorItemReader<T> build() {
334                if(this.saveState) {
335                        Assert.hasText(this.name,
336                                        "A name is required when saveSate is set to true");
337                }
338
339                Assert.hasText(this.sql, "A query is required");
340                Assert.notNull(this.dataSource, "A datasource is required");
341                Assert.notNull(this.rowMapper, "A rowmapper is required");
342
343                JdbcCursorItemReader<T> reader = new JdbcCursorItemReader<>();
344
345                if(StringUtils.hasText(this.name)) {
346                        reader.setName(this.name);
347                }
348
349                reader.setSaveState(this.saveState);
350                reader.setPreparedStatementSetter(this.preparedStatementSetter);
351                reader.setRowMapper(this.rowMapper);
352                reader.setSql(this.sql);
353                reader.setCurrentItemCount(this.currentItemCount);
354                reader.setDataSource(this.dataSource);
355                reader.setDriverSupportsAbsolute(this.driverSupportsAbsolute);
356                reader.setFetchSize(this.fetchSize);
357                reader.setIgnoreWarnings(this.ignoreWarnings);
358                reader.setMaxItemCount(this.maxItemCount);
359                reader.setMaxRows(this.maxRows);
360                reader.setQueryTimeout(this.queryTimeout);
361                reader.setUseSharedExtendedConnection(this.useSharedExtendedConnection);
362                reader.setVerifyCursorPosition(this.verifyCursorPosition);
363
364                return reader;
365        }
366 }