001/*
002 * Copyright 2017-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 javax.sql.DataSource;
019
020import org.springframework.batch.item.database.AbstractCursorItemReader;
021
022import org.springframework.batch.item.database.StoredProcedureItemReader;
023import org.springframework.jdbc.core.PreparedStatementSetter;
024import org.springframework.jdbc.core.RowMapper;
025import org.springframework.jdbc.core.SqlParameter;
026import org.springframework.util.Assert;
027import org.springframework.util.StringUtils;
028
029/**
030 * A fluent builder API for the configuration of a {@link StoredProcedureItemReader}.
031 *
032 * @author Michael Minella
033 * @author Mahmoud Ben Hassine
034 * @since 4.0.0
035 * @see StoredProcedureItemReader
036 */
037public class StoredProcedureItemReaderBuilder<T> {
038
039        public static final int VALUE_NOT_SET = -1;
040
041        private int currentItemCount = 0;
042
043        private int maxItemCount = Integer.MAX_VALUE;
044
045        private boolean saveState = true;
046
047        private DataSource dataSource;
048
049        private int fetchSize = VALUE_NOT_SET;
050
051        private int maxRows = VALUE_NOT_SET;
052
053        private int queryTimeout = VALUE_NOT_SET;
054
055        private boolean ignoreWarnings = true;
056
057        private boolean verifyCursorPosition = true;
058
059        private boolean driverSupportsAbsolute = false;
060
061        private boolean useSharedExtendedConnection = false;
062
063        private PreparedStatementSetter preparedStatementSetter;
064
065        private RowMapper<T> rowMapper;
066
067        private String procedureName;
068
069        private SqlParameter[] parameters = new SqlParameter[0];
070
071        private boolean function = false;
072
073        private int refCursorPosition = 0;
074
075        private String name;
076
077        /**
078         * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport}
079         * should be persisted within the {@link org.springframework.batch.item.ExecutionContext}
080         * for restart purposes.
081         *
082         * @param saveState defaults to true
083         * @return The current instance of the builder.
084         */
085        public StoredProcedureItemReaderBuilder<T> saveState(boolean saveState) {
086                this.saveState = saveState;
087
088                return this;
089        }
090
091        /**
092         * The name used to calculate the key within the
093         * {@link org.springframework.batch.item.ExecutionContext}. Required if
094         * {@link #saveState(boolean)} is set to true.
095         *
096         * @param name name of the reader instance
097         * @return The current instance of the builder.
098         * @see org.springframework.batch.item.ItemStreamSupport#setName(String)
099         */
100        public StoredProcedureItemReaderBuilder<T> name(String name) {
101                this.name = name;
102
103                return this;
104        }
105
106        /**
107         * Configure the max number of items to be read.
108         *
109         * @param maxItemCount the max items to be read
110         * @return The current instance of the builder.
111         * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int)
112         */
113        public StoredProcedureItemReaderBuilder<T> maxItemCount(int maxItemCount) {
114                this.maxItemCount = maxItemCount;
115
116                return this;
117        }
118
119        /**
120         * Index for the current item. Used on restarts to indicate where to start from.
121         *
122         * @param currentItemCount current index
123         * @return this instance for method chaining
124         * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int)
125         */
126        public StoredProcedureItemReaderBuilder<T> currentItemCount(int currentItemCount) {
127                this.currentItemCount = currentItemCount;
128
129                return this;
130        }
131
132        /**
133         * The {@link DataSource} to read from
134         *
135         * @param dataSource a relational data base
136         * @return this instance for method chaining
137         * @see StoredProcedureItemReader#setDataSource(DataSource)
138         */
139        public StoredProcedureItemReaderBuilder<T> dataSource(DataSource dataSource) {
140                this.dataSource = dataSource;
141
142                return this;
143        }
144
145        /**
146         * A hint to the driver as to how many rows to return with each fetch.
147         *
148         * @param fetchSize the hint
149         * @return this instance for method chaining
150         * @see StoredProcedureItemReader#setFetchSize(int)
151         */
152        public StoredProcedureItemReaderBuilder<T> fetchSize(int fetchSize) {
153                this.fetchSize = fetchSize;
154
155                return this;
156        }
157
158        /**
159         * The max number of rows the {@link java.sql.ResultSet} can contain
160         *
161         * @param maxRows the max
162         * @return this instance for method chaining
163         * @see StoredProcedureItemReader#setMaxRows(int)
164         */
165        public StoredProcedureItemReaderBuilder<T> maxRows(int maxRows) {
166                this.maxRows = maxRows;
167
168                return this;
169        }
170
171        /**
172         * The time in milliseconds for the query to timeout
173         *
174         * @param queryTimeout timeout
175         * @return this instance for method chaining
176         * @see StoredProcedureItemReader#setQueryTimeout(int)
177         */
178        public StoredProcedureItemReaderBuilder<T> queryTimeout(int queryTimeout) {
179                this.queryTimeout = queryTimeout;
180
181                return this;
182        }
183
184        /**
185         * Indicates if SQL warnings should be ignored or if an exception should be thrown.
186         *
187         * @param ignoreWarnings indicator. Defaults to true
188         * @return this instance for method chaining
189         * @see AbstractCursorItemReader#setIgnoreWarnings(boolean)
190         */
191        public StoredProcedureItemReaderBuilder<T> ignoreWarnings(boolean ignoreWarnings) {
192                this.ignoreWarnings = ignoreWarnings;
193
194                return this;
195        }
196
197        /**
198         * Indicates if the reader should verify the current position of the
199         * {@link java.sql.ResultSet} after being passed to the {@link RowMapper}.  Defaults
200         * to true.
201         *
202         * @param verifyCursorPosition indicator
203         * @return this instance for method chaining
204         * @see StoredProcedureItemReader#setVerifyCursorPosition(boolean)
205         */
206        public StoredProcedureItemReaderBuilder<T> verifyCursorPosition(boolean verifyCursorPosition) {
207                this.verifyCursorPosition = verifyCursorPosition;
208
209                return this;
210        }
211
212        /**
213         * Indicates if the JDBC driver supports setting the absolute row on the
214         * {@link java.sql.ResultSet}.
215         *
216         * @param driverSupportsAbsolute indicator
217         * @return this instance for method chaining
218         * @see StoredProcedureItemReader#setDriverSupportsAbsolute(boolean)
219         */
220        public StoredProcedureItemReaderBuilder<T> driverSupportsAbsolute(boolean driverSupportsAbsolute) {
221                this.driverSupportsAbsolute = driverSupportsAbsolute;
222
223                return this;
224        }
225
226        /**
227         * Indicates that the connection used for the cursor is being used by all other
228         * processing, therefor part of the same transaction.
229         *
230         * @param useSharedExtendedConnection indicator
231         * @return this instance for method chaining
232         * @see StoredProcedureItemReader#setUseSharedExtendedConnection(boolean)
233         */
234        public StoredProcedureItemReaderBuilder<T> useSharedExtendedConnection(boolean useSharedExtendedConnection) {
235                this.useSharedExtendedConnection = useSharedExtendedConnection;
236
237                return this;
238        }
239
240        /**
241         * Configures the provided {@link PreparedStatementSetter} to be used to populate any
242         * arguments in the SQL query to be executed for the reader.
243         *
244         * @param preparedStatementSetter setter
245         * @return this instance for method chaining
246         * @see StoredProcedureItemReader#setPreparedStatementSetter(PreparedStatementSetter)
247         */
248        public StoredProcedureItemReaderBuilder<T> preparedStatementSetter(PreparedStatementSetter preparedStatementSetter) {
249                this.preparedStatementSetter = preparedStatementSetter;
250
251                return this;
252        }
253
254        /**
255         * The {@link RowMapper} used to map the results of the cursor to each item.
256         *
257         * @param rowMapper {@link RowMapper}
258         * @return this instance for method chaining
259         * @see StoredProcedureItemReader#setRowMapper(RowMapper)
260         */
261        public StoredProcedureItemReaderBuilder<T> rowMapper(RowMapper<T> rowMapper) {
262                this.rowMapper = rowMapper;
263
264                return this;
265        }
266
267        /**
268         * The name of the stored procedure to execute
269         *
270         * @param procedureName name of the procedure
271         * @return this instance for method chaining
272         * @see StoredProcedureItemReader#setProcedureName(String)
273         */
274        public StoredProcedureItemReaderBuilder<T> procedureName(String procedureName) {
275                this.procedureName = procedureName;
276
277                return this;
278        }
279
280        /**
281         * SQL parameters to be set when executing the stored procedure
282         *
283         * @param parameters parameters to be set
284         * @return this instance for method chaining
285         * @see StoredProcedureItemReader#setParameters(SqlParameter[])
286         */
287        public StoredProcedureItemReaderBuilder<T> parameters(SqlParameter[] parameters) {
288                this.parameters = parameters;
289
290                return this;
291        }
292
293        /**
294         * Indicates the stored procedure is a function
295         *
296         * @return this instance for method chaining
297         * @see StoredProcedureItemReader#setFunction(boolean)
298         */
299        public StoredProcedureItemReaderBuilder<T> function() {
300                this.function = true;
301
302                return this;
303        }
304
305        /**
306         * The parameter position of the REF CURSOR.  Only used for Oracle and PostgreSQL that
307         * use REF CURSORs.  For any other database, this should remain as the default (0).
308         *
309         * @param refCursorPosition the parameter position
310         * @return this instance for method chaining
311         * @see StoredProcedureItemReader#setRefCursorPosition(int)
312         */
313        public StoredProcedureItemReaderBuilder<T> refCursorPosition(int refCursorPosition) {
314                this.refCursorPosition = refCursorPosition;
315
316                return this;
317        }
318
319        /**
320         * Validates configuration and builds a new reader instance
321         *
322         * @return a fully constructed {@link StoredProcedureItemReader}
323         */
324        public StoredProcedureItemReader<T> build() {
325                if(this.saveState) {
326                        Assert.hasText(this.name,
327                                        "A name is required when saveSate is set to true");
328                }
329
330                Assert.notNull(this.procedureName, "The name of the stored procedure must be provided");
331                Assert.notNull(this.dataSource, "A datasource is required");
332                Assert.notNull(this.rowMapper, "A rowmapper is required");
333
334                StoredProcedureItemReader<T> itemReader = new StoredProcedureItemReader<>();
335
336                if(StringUtils.hasText(this.name)) {
337                        itemReader.setName(this.name);
338                }
339
340                itemReader.setProcedureName(this.procedureName);
341                itemReader.setRowMapper(this.rowMapper);
342                itemReader.setParameters(this.parameters);
343                itemReader.setPreparedStatementSetter(this.preparedStatementSetter);
344                itemReader.setFunction(this.function);
345                itemReader.setRefCursorPosition(this.refCursorPosition);
346                itemReader.setCurrentItemCount(this.currentItemCount);
347                itemReader.setDataSource(this.dataSource);
348                itemReader.setDriverSupportsAbsolute(this.driverSupportsAbsolute);
349                itemReader.setFetchSize(this.fetchSize);
350                itemReader.setIgnoreWarnings(this.ignoreWarnings);
351                itemReader.setMaxItemCount(this.maxItemCount);
352                itemReader.setMaxRows(this.maxRows);
353                itemReader.setQueryTimeout(this.queryTimeout);
354                itemReader.setSaveState(this.saveState);
355                itemReader.setUseSharedExtendedConnection(this.useSharedExtendedConnection);
356                itemReader.setVerifyCursorPosition(this.verifyCursorPosition);
357
358                return itemReader;
359        }
360
361}