From d84057c2b1f077c4d48b41305f00178f4abbf138 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Aug 2023 09:47:27 +0200 Subject: [PATCH] Refine contribution #4323 - Update parameter types - Update tests - Update Javadocs - Fix code formatting --- .../item/data/MongoCursorItemReader.java | 512 ++++++++--------- .../builder/MongoCursorItemReaderBuilder.java | 519 +++++++++--------- .../item/data/MongoCursorItemReaderTest.java | 438 +++++++-------- .../MongoCursorItemReaderBuilderTests.java | 70 +++ 4 files changed, 815 insertions(+), 724 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoCursorItemReader.java index 4c3c8b0dfe..1759557d61 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoCursorItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoCursorItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2023 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. @@ -16,7 +16,6 @@ package org.springframework.batch.item.data; import java.time.Duration; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -26,6 +25,7 @@ import org.bson.Document; import org.bson.codecs.DecoderContext; +import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.domain.Sort; @@ -40,258 +40,268 @@ import org.springframework.util.StringUtils; /** + * Cursor-based {@link ItemReader} implementation for MongoDB. + * * @author LEE Juchan - * @since 5.0 + * @author Mahmoud Ben Hassine + * @since 5.1 */ public class MongoCursorItemReader extends AbstractItemCountingItemStreamItemReader implements InitializingBean { - private MongoOperations template; - - private Class targetType; - - private String collection; - - private Query query; - - private String queryString; - - private List parameterValues = new ArrayList<>(); - - private String fields; - - private Sort sort; - - private String hint; - - private Integer batchSize; - - private Integer limit; - - private Integer maxTimeMs; - - private CloseableIterator cursor; - - public MongoCursorItemReader() { - super(); - setName(ClassUtils.getShortName(MongoCursorItemReader.class)); - } - - /** - * Used to perform operations against the MongoDB instance. Also handles the mapping - * of documents to objects. - * @param template the MongoOperations instance to use - * @see MongoOperations - */ - public void setTemplate(MongoOperations template) { - this.template = template; - } - - /** - * The targetType of object to be returned for each {@link #read()} call. - * @param targetType the targetType of object to return - */ - public void setTargetType(Class targetType) { - this.targetType = targetType; - } - - /** - * @param collection Mongo collection to be queried. - */ - public void setCollection(String collection) { - this.collection = collection; - } - - /** - * A Mongo Query to be used. - * @param query Mongo Query to be used. - */ - public void setQuery(Query query) { - this.query = query; - } - - /** - * A JSON formatted MongoDB query. Parameterization of the provided query is allowed - * via ?<index> placeholders where the <index> indicates the index of the - * parameterValue to substitute. - * @param queryString JSON formatted Mongo query - */ - public void setQuery(String queryString) { - this.queryString = queryString; - } - - /** - * {@link List} of values to be substituted in for each of the parameters in the - * query. - * @param parameterValues values - */ - public void setParameterValues(List parameterValues) { - Assert.notNull(parameterValues, "Parameter values must not be null"); - this.parameterValues = parameterValues; - } - - /** - * JSON defining the fields to be returned from the matching documents by MongoDB. - * @param fields JSON string that identifies the fields to sort by. - */ - public void setFields(String fields) { - this.fields = fields; - } - - /** - * {@link Map} of property - * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the - * input by. - * @param sorts map of properties and direction to sort each. - */ - public void setSort(Map sorts) { - Assert.notNull(sorts, "Sorts must not be null"); - this.sort = convertToSort(sorts); - } - - /** - * JSON String telling MongoDB what index to use. - * @param hint string indicating what index to use. - */ - public void setHint(String hint) { - this.hint = hint; - } - - /** - * The size of batches to use when iterating over results. - * @param batchSize size the batch size to apply to the cursor - */ - public void setBatchSize(Integer batchSize) { - this.batchSize = batchSize; - } - - /** - * The query limit - * @param limit The limit - */ - public void setLimit(Integer limit) { - this.limit = limit; - } - - /** - * The maximum execution time for the aggregation command - * @param maxTimeMs The max time - */ - public void setMaxTimeMs(Integer maxTimeMs) { - this.maxTimeMs = maxTimeMs; - } - - /** - * Checks mandatory properties - * - * @see InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() { - Assert.state(template != null, "An implementation of MongoOperations is required."); - Assert.state(targetType != null, "A targetType to convert the input into is required."); - Assert.state(queryString != null || query != null, "A query is required."); - - if (queryString != null) { - Assert.state(sort != null, "A sort is required."); - } - } - - @Override - protected void doOpen() throws Exception { - Query mongoQuery; - if (queryString != null) { - mongoQuery = createQuery(); - } else { - mongoQuery = query; - } - - Stream stream; - if (StringUtils.hasText(collection)) { - stream = template.stream(mongoQuery, targetType, collection); - } else { - stream = template.stream(mongoQuery, targetType); - } - - this.cursor = streamToIterator(stream); - } - - @Override - protected T doRead() throws Exception { - return cursor.hasNext() ? cursor.next() : null; - } - - @Override - protected void doClose() throws Exception { - this.cursor.close(); - } - - private Sort convertToSort(Map sorts) { - List sortValues = new ArrayList<>(sorts.size()); - - for (Map.Entry curSort : sorts.entrySet()) { - sortValues.add(new Sort.Order(curSort.getValue(), curSort.getKey())); - } - - return Sort.by(sortValues); - } - - private Query createQuery() { - String populatedQuery = replacePlaceholders(queryString, parameterValues); - - Query mongoQuery; - if (StringUtils.hasText(fields)) { - mongoQuery = new BasicQuery(populatedQuery, fields); - } else { - mongoQuery = new BasicQuery(populatedQuery); - } - - if (sort != null) { - mongoQuery.with(sort); - } - if (StringUtils.hasText(hint)) { - mongoQuery.withHint(hint); - } - if (batchSize != null) { - mongoQuery.cursorBatchSize(batchSize); - } - if (limit != null) { - mongoQuery.limit(limit); - } - if (maxTimeMs != null) { - mongoQuery.maxTime(Duration.of(maxTimeMs, ChronoUnit.MILLIS)); - } else { - mongoQuery.noCursorTimeout(); - } - - return mongoQuery; - } - - private String replacePlaceholders(String input, List values) { - ParameterBindingJsonReader reader = new ParameterBindingJsonReader(input, values.toArray()); - DecoderContext decoderContext = DecoderContext.builder().build(); - Document document = new ParameterBindingDocumentCodec().decode(reader, decoderContext); - return document.toJson(); - } - - private CloseableIterator streamToIterator(Stream stream) { - return new CloseableIterator<>() { - final private Iterator delegate = stream.iterator(); - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public T next() { - return delegate.next(); - } - - @Override - public void close() { - stream.close(); - } - }; - } + private MongoOperations template; + + private Class targetType; + + private String collection; + + private Query query; + + private String queryString; + + private List parameterValues = new ArrayList<>(); + + private String fields; + + private Sort sort; + + private String hint; + + private int batchSize; + + private int limit; + + private Duration maxTime; + + private CloseableIterator cursor; + + /** + * Create a new {@link MongoCursorItemReader}. + */ + public MongoCursorItemReader() { + super(); + setName(ClassUtils.getShortName(MongoCursorItemReader.class)); + } + + /** + * Used to perform operations against the MongoDB instance. Also handles the mapping + * of documents to objects. + * @param template the MongoOperations instance to use + * @see MongoOperations + */ + public void setTemplate(MongoOperations template) { + this.template = template; + } + + /** + * The targetType of object to be returned for each {@link #read()} call. + * @param targetType the targetType of object to return + */ + public void setTargetType(Class targetType) { + this.targetType = targetType; + } + + /** + * @param collection Mongo collection to be queried. + */ + public void setCollection(String collection) { + this.collection = collection; + } + + /** + * A Mongo Query to be used. + * @param query Mongo Query to be used. + */ + public void setQuery(Query query) { + this.query = query; + } + + /** + * A JSON formatted MongoDB query. Parameterization of the provided query is allowed + * via ?<index> placeholders where the <index> indicates the index of the + * parameterValue to substitute. + * @param queryString JSON formatted Mongo query + */ + public void setQuery(String queryString) { + this.queryString = queryString; + } + + /** + * {@link List} of values to be substituted in for each of the parameters in the + * query. + * @param parameterValues values + */ + public void setParameterValues(List parameterValues) { + Assert.notNull(parameterValues, "Parameter values must not be null"); + this.parameterValues = parameterValues; + } + + /** + * JSON defining the fields to be returned from the matching documents by MongoDB. + * @param fields JSON string that identifies the fields to sort by. + */ + public void setFields(String fields) { + this.fields = fields; + } + + /** + * {@link Map} of property + * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the + * input by. + * @param sorts map of properties and direction to sort each. + */ + public void setSort(Map sorts) { + Assert.notNull(sorts, "Sorts must not be null"); + this.sort = convertToSort(sorts); + } + + /** + * JSON String telling MongoDB what index to use. + * @param hint string indicating what index to use. + */ + public void setHint(String hint) { + this.hint = hint; + } + + /** + * The size of batches to use when iterating over results. + * @param batchSize size the batch size to apply to the cursor + * @see Query#cursorBatchSize(int) + */ + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + /** + * The query limit. + * @param limit The limit + * @see Query#limit(int) + */ + public void setLimit(int limit) { + this.limit = limit; + } + + /** + * The maximum execution time for the query + * @param maxTime The max time + * @see Query#maxTime(Duration) + */ + public void setMaxTime(Duration maxTime) { + Assert.notNull(maxTime, "maxTime must not be null."); + this.maxTime = maxTime; + } + + /** + * Checks mandatory properties + * + * @see InitializingBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() { + Assert.state(template != null, "An implementation of MongoOperations is required."); + Assert.state(targetType != null, "A targetType to convert the input into is required."); + Assert.state(queryString != null || query != null, "A query is required."); + + if (queryString != null) { + Assert.state(sort != null, "A sort is required."); + } + } + + @Override + protected void doOpen() throws Exception { + Query mongoQuery; + if (queryString != null) { + mongoQuery = createQuery(); + } + else { + mongoQuery = query; + } + + Stream stream; + if (StringUtils.hasText(collection)) { + stream = template.stream(mongoQuery, targetType, collection); + } + else { + stream = template.stream(mongoQuery, targetType); + } + + this.cursor = streamToIterator(stream); + } + + @Override + protected T doRead() throws Exception { + return cursor.hasNext() ? cursor.next() : null; + } + + @Override + protected void doClose() throws Exception { + this.cursor.close(); + } + + private Sort convertToSort(Map sorts) { + List sortValues = new ArrayList<>(sorts.size()); + + for (Map.Entry curSort : sorts.entrySet()) { + sortValues.add(new Sort.Order(curSort.getValue(), curSort.getKey())); + } + + return Sort.by(sortValues); + } + + private Query createQuery() { + String populatedQuery = replacePlaceholders(queryString, parameterValues); + + Query mongoQuery; + if (StringUtils.hasText(fields)) { + mongoQuery = new BasicQuery(populatedQuery, fields); + } + else { + mongoQuery = new BasicQuery(populatedQuery); + } + + if (sort != null) { + mongoQuery.with(sort); + } + if (StringUtils.hasText(hint)) { + mongoQuery.withHint(hint); + } + mongoQuery.cursorBatchSize(batchSize); + mongoQuery.limit(limit); + if (maxTime != null) { + mongoQuery.maxTime(maxTime); + } + else { + mongoQuery.noCursorTimeout(); + } + + return mongoQuery; + } + + private String replacePlaceholders(String input, List values) { + ParameterBindingJsonReader reader = new ParameterBindingJsonReader(input, values.toArray()); + DecoderContext decoderContext = DecoderContext.builder().build(); + Document document = new ParameterBindingDocumentCodec().decode(reader, decoderContext); + return document.toJson(); + } + + private CloseableIterator streamToIterator(Stream stream) { + return new CloseableIterator<>() { + final private Iterator delegate = stream.iterator(); + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public T next() { + return delegate.next(); + } + + @Override + public void close() { + stream.close(); + } + }; + } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilder.java index 6b39adf000..b7c09835b7 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2023 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. @@ -16,6 +16,7 @@ package org.springframework.batch.item.data.builder; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -29,276 +30,282 @@ /** * @author LEE Juchan - * @since 5.0 + * @author Mahmoud Ben Hassine + * @since 5.1 * @see MongoCursorItemReader */ public class MongoCursorItemReaderBuilder { - private boolean saveState = true; + private boolean saveState = true; - private String name; + private String name; - private int maxItemCount = Integer.MAX_VALUE; + private int maxItemCount = Integer.MAX_VALUE; - private int currentItemCount; + private int currentItemCount; - private MongoOperations template; + private MongoOperations template; - private Class targetType; + private Class targetType; - private String collection; + private String collection; - private Query query; + private Query query; - private String jsonQuery; + private String jsonQuery; - private List parameterValues = new ArrayList<>(); + private List parameterValues = new ArrayList<>(); - private String fields; + private String fields; - private Map sorts; + private Map sorts; - private String hint; + private String hint; + + private int batchSize; + + private int limit; + + private Duration maxTime; + + /** + * Configure if the state of the + * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within + * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. + * @param saveState defaults to true + * @return The current instance of the builder. + */ + public MongoCursorItemReaderBuilder saveState(boolean saveState) { + this.saveState = saveState; + + return this; + } + + /** + * The name used to calculate the key within the + * {@link org.springframework.batch.item.ExecutionContext}. Required if + * {@link #saveState(boolean)} is set to true. + * @param name name of the reader instance + * @return The current instance of the builder. + * @see org.springframework.batch.item.ItemStreamSupport#setName(String) + */ + public MongoCursorItemReaderBuilder name(String name) { + this.name = name; + + return this; + } + + /** + * Configure the max number of items to be read. + * @param maxItemCount the max items to be read + * @return The current instance of the builder. + * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) + */ + public MongoCursorItemReaderBuilder maxItemCount(int maxItemCount) { + this.maxItemCount = maxItemCount; + + return this; + } + + /** + * Index for the current item. Used on restarts to indicate where to start from. + * @param currentItemCount current index + * @return this instance for method chaining + * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) + */ + public MongoCursorItemReaderBuilder currentItemCount(int currentItemCount) { + this.currentItemCount = currentItemCount; + + return this; + } + + /** + * Used to perform operations against the MongoDB instance. Also handles the mapping + * of documents to objects. + * @param template the MongoOperations instance to use + * @see MongoOperations + * @return The current instance of the builder + * @see MongoCursorItemReader#setTemplate(MongoOperations) + */ + public MongoCursorItemReaderBuilder template(MongoOperations template) { + this.template = template; + + return this; + } + + /** + * The targetType of object to be returned for each + * {@link MongoCursorItemReader#read()} call. + * @param targetType the targetType of object to return + * @return The current instance of the builder + * @see MongoCursorItemReader#setTargetType(Class) + */ + public MongoCursorItemReaderBuilder targetType(Class targetType) { + this.targetType = targetType; + + return this; + } + + /** + * Establish an optional collection that can be queried. + * @param collection Mongo collection to be queried. + * @return The current instance of the builder + * @see MongoCursorItemReader#setCollection(String) + */ + public MongoCursorItemReaderBuilder collection(String collection) { + this.collection = collection; + + return this; + } + + /** + * Provide a Spring Data Mongo {@link Query}. This will take precedence over a JSON + * configured query. + * @param query Query to execute + * @return this instance for method chaining + * @see MongoCursorItemReader#setQuery(Query) + */ + public MongoCursorItemReaderBuilder query(Query query) { + this.query = query; + + return this; + } + + /** + * A JSON formatted MongoDB jsonQuery. Parameterization of the provided jsonQuery is + * allowed via ?<index> placeholders where the <index> indicates the index + * of the parameterValue to substitute. + * @param query JSON formatted Mongo jsonQuery + * @return The current instance of the builder + * @see MongoCursorItemReader#setQuery(String) + */ + public MongoCursorItemReaderBuilder jsonQuery(String query) { + this.jsonQuery = query; + + return this; + } + + /** + * Values to be substituted in for each of the parameters in the query. + * @param parameterValues values + * @return The current instance of the builder + * @see MongoCursorItemReader#setParameterValues(List) + */ + public MongoCursorItemReaderBuilder parameterValues(List parameterValues) { + this.parameterValues = parameterValues; + + return this; + } + + /** + * JSON defining the fields to be returned from the matching documents by MongoDB. + * @param fields JSON string that identifies the fields to sort by. + * @return The current instance of the builder + * @see MongoCursorItemReader#setFields(String) + */ + public MongoCursorItemReaderBuilder fields(String fields) { + this.fields = fields; + + return this; + } + + /** + * {@link Map} of property + * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the + * input by. + * @param sorts map of properties and direction to sort each. + * @return The current instance of the builder + * @see MongoCursorItemReader#setSort(Map) + */ + public MongoCursorItemReaderBuilder sorts(Map sorts) { + this.sorts = sorts; + + return this; + } + + /** + * JSON String telling MongoDB what index to use. + * @param hint string indicating what index to use. + * @return The current instance of the builder + * @see MongoCursorItemReader#setHint(String) + */ + public MongoCursorItemReaderBuilder hint(String hint) { + this.hint = hint; + + return this; + } + + /** + * The size of batches to use when iterating over results. + * @param batchSize string indicating what index to use. + * @return The current instance of the builder + * @see MongoCursorItemReader#setHint(String) + */ + public MongoCursorItemReaderBuilder batchSize(int batchSize) { + this.batchSize = batchSize; + + return this; + } + + /** + * The query limit + * @param limit The limit + * @return The current instance of the builder + * @see MongoCursorItemReader#setLimit(int) + */ + public MongoCursorItemReaderBuilder limit(int limit) { + this.limit = limit; + + return this; + } + + /** + * The maximum execution time for the query + * @param maxTime The max time + * @return The current instance of the builder + * @see MongoCursorItemReader#setMaxTime(Duration) + */ + public MongoCursorItemReaderBuilder maxTime(Duration maxTime) { + Assert.notNull(maxTime, "maxTime must not be null."); + this.maxTime = maxTime; + + return this; + } + + public MongoCursorItemReader build() { + Assert.notNull(this.template, "template is required."); + if (this.saveState) { + Assert.hasText(this.name, "A name is required when saveState is set to true"); + } + Assert.notNull(this.targetType, "targetType is required."); + Assert.state(StringUtils.hasText(this.jsonQuery) || this.query != null, "A query is required"); + + if (StringUtils.hasText(this.jsonQuery) || this.query != null) { + Assert.notNull(this.sorts, "sorts map is required."); + } + + MongoCursorItemReader reader = new MongoCursorItemReader<>(); + reader.setSaveState(this.saveState); + reader.setName(this.name); + reader.setCurrentItemCount(this.currentItemCount); + reader.setMaxItemCount(this.maxItemCount); + + reader.setTemplate(this.template); + reader.setTargetType(this.targetType); + reader.setCollection(this.collection); + reader.setQuery(this.query); + reader.setQuery(this.jsonQuery); + reader.setParameterValues(this.parameterValues); + reader.setFields(this.fields); + reader.setSort(this.sorts); + reader.setHint(this.hint); + reader.setBatchSize(this.batchSize); + reader.setLimit(this.limit); + if (this.maxTime != null) { + reader.setMaxTime(this.maxTime); + } + + return reader; + } - private Integer batchSize; - - private Integer limit; - - private Integer maxTimeMs; - - /** - * Configure if the state of the - * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within - * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. - * @param saveState defaults to true - * @return The current instance of the builder. - */ - public MongoCursorItemReaderBuilder saveState(boolean saveState) { - this.saveState = saveState; - - return this; - } - - /** - * The name used to calculate the key within the - * {@link org.springframework.batch.item.ExecutionContext}. Required if - * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance - * @return The current instance of the builder. - * @see org.springframework.batch.item.ItemStreamSupport#setName(String) - */ - public MongoCursorItemReaderBuilder name(String name) { - this.name = name; - - return this; - } - - /** - * Configure the max number of items to be read. - * @param maxItemCount the max items to be read - * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) - */ - public MongoCursorItemReaderBuilder maxItemCount(int maxItemCount) { - this.maxItemCount = maxItemCount; - - return this; - } - - /** - * Index for the current item. Used on restarts to indicate where to start from. - * @param currentItemCount current index - * @return this instance for method chaining - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) - */ - public MongoCursorItemReaderBuilder currentItemCount(int currentItemCount) { - this.currentItemCount = currentItemCount; - - return this; - } - - /** - * Used to perform operations against the MongoDB instance. Also handles the mapping - * of documents to objects. - * @param template the MongoOperations instance to use - * @see MongoOperations - * @return The current instance of the builder - * @see MongoCursorItemReader#setTemplate(MongoOperations) - */ - public MongoCursorItemReaderBuilder template(MongoOperations template) { - this.template = template; - - return this; - } - - /** - * The targetType of object to be returned for each {@link MongoCursorItemReader#read()} call. - * @param targetType the targetType of object to return - * @return The current instance of the builder - * @see MongoCursorItemReader#setTargetType(Class) - */ - public MongoCursorItemReaderBuilder targetType(Class targetType) { - this.targetType = targetType; - - return this; - } - - /** - * Establish an optional collection that can be queried. - * @param collection Mongo collection to be queried. - * @return The current instance of the builder - * @see MongoCursorItemReader#setCollection(String) - */ - public MongoCursorItemReaderBuilder collection(String collection) { - this.collection = collection; - - return this; - } - - /** - * Provide a Spring Data Mongo {@link Query}. This will take precedence over a JSON - * configured query. - * @param query Query to execute - * @return this instance for method chaining - * @see MongoCursorItemReader#setQuery(Query) - */ - public MongoCursorItemReaderBuilder query(Query query) { - this.query = query; - - return this; - } - - /** - * A JSON formatted MongoDB jsonQuery. Parameterization of the provided jsonQuery is - * allowed via ?<index> placeholders where the <index> indicates the index - * of the parameterValue to substitute. - * @param query JSON formatted Mongo jsonQuery - * @return The current instance of the builder - * @see MongoCursorItemReader#setQuery(String) - */ - public MongoCursorItemReaderBuilder jsonQuery(String query) { - this.jsonQuery = query; - - return this; - } - - /** - * Values to be substituted in for each of the parameters in the query. - * @param parameterValues values - * @return The current instance of the builder - * @see MongoCursorItemReader#setParameterValues(List) - */ - public MongoCursorItemReaderBuilder parameterValues(List parameterValues) { - this.parameterValues = parameterValues; - - return this; - } - - /** - * JSON defining the fields to be returned from the matching documents by MongoDB. - * @param fields JSON string that identifies the fields to sort by. - * @return The current instance of the builder - * @see MongoCursorItemReader#setFields(String) - */ - public MongoCursorItemReaderBuilder fields(String fields) { - this.fields = fields; - - return this; - } - - /** - * {@link Map} of property - * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the - * input by. - * @param sorts map of properties and direction to sort each. - * @return The current instance of the builder - * @see MongoCursorItemReader#setSort(Map) - */ - public MongoCursorItemReaderBuilder sorts(Map sorts) { - this.sorts = sorts; - - return this; - } - - /** - * JSON String telling MongoDB what index to use. - * @param hint string indicating what index to use. - * @return The current instance of the builder - * @see MongoCursorItemReader#setHint(String) - */ - public MongoCursorItemReaderBuilder hint(String hint) { - this.hint = hint; - - return this; - } - - /** - * The size of batches to use when iterating over results. - * @param batchSize string indicating what index to use. - * @return The current instance of the builder - * @see MongoCursorItemReader#setHint(String) - */ - public MongoCursorItemReaderBuilder batchSize(Integer batchSize) { - this.batchSize = batchSize; - - return this; - } - - /** - * The query limit - * @param limit The limit - * @return The current instance of the builder - * @see MongoCursorItemReader#setLimit(Integer) - */ - public MongoCursorItemReaderBuilder limit(Integer limit) { - this.limit = limit; - - return this; - } - - /** - * The maximum execution time for the aggregation command - * @param maxTimeMs The max time - * @return The current instance of the builder - * @see MongoCursorItemReader#setMaxTimeMs(Integer) - */ - public MongoCursorItemReaderBuilder maxTimeMs(Integer maxTimeMs) { - this.maxTimeMs = maxTimeMs; - - return this; - } - - public MongoCursorItemReader build() { - Assert.notNull(this.template, "template is required."); - if (this.saveState) { - Assert.hasText(this.name, "A name is required when saveState is set to true"); - } - Assert.notNull(this.targetType, "targetType is required."); - Assert.state(StringUtils.hasText(this.jsonQuery) || this.query != null, "A query is required"); - - if (StringUtils.hasText(this.jsonQuery) || this.query != null) { - Assert.notNull(this.sorts, "sorts map is required."); - } - - MongoCursorItemReader reader = new MongoCursorItemReader<>(); - reader.setSaveState(this.saveState); - reader.setName(this.name); - reader.setCurrentItemCount(this.currentItemCount); - reader.setMaxItemCount(this.maxItemCount); - - reader.setTemplate(this.template); - reader.setTargetType(this.targetType); - reader.setCollection(this.collection); - reader.setQuery(this.query); - reader.setQuery(this.jsonQuery); - reader.setParameterValues(this.parameterValues); - reader.setFields(this.fields); - reader.setSort(this.sorts); - reader.setHint(this.hint); - reader.setBatchSize(this.batchSize); - reader.setLimit(this.limit); - reader.setMaxTimeMs(this.maxTimeMs); - - return reader; - } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java index 107d373e8f..8cb6bf84c7 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2023 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. @@ -15,6 +15,7 @@ */ package org.springframework.batch.item.data; +import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -37,274 +38,277 @@ import static org.mockito.Mockito.when; /** + * Test class for {@link MongoCursorItemReader}. + * * @author LEE Juchan - * @since 5.0 + * @author Mahmoud Ben Hassine */ @ExtendWith(MockitoExtension.class) class MongoCursorItemReaderTest { - private MongoCursorItemReader reader; - - @Mock - private MongoTemplate template; - - private Map sortOptions; + private MongoCursorItemReader reader; - @BeforeEach - void setUp() { - reader = new MongoCursorItemReader<>(); + @Mock + private MongoTemplate template; - sortOptions = new HashMap<>(); - sortOptions.put("name", Sort.Direction.DESC); + private Map sortOptions; - reader.setTemplate(template); - reader.setTargetType(String.class); - reader.setQuery("{ }"); - reader.setSort(sortOptions); - reader.afterPropertiesSet(); - } + @BeforeEach + void setUp() { + reader = new MongoCursorItemReader<>(); - @Test - void testAfterPropertiesSetForQueryString() { - reader = new MongoCursorItemReader<>(); - Exception exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("An implementation of MongoOperations is required.", exception.getMessage()); + sortOptions = new HashMap<>(); + sortOptions.put("name", Sort.Direction.DESC); - reader.setTemplate(template); + reader.setTemplate(template); + reader.setTargetType(String.class); + reader.setQuery("{ }"); + reader.setSort(sortOptions); + reader.afterPropertiesSet(); + } - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A type to convert the input into is required.", exception.getMessage()); + @Test + void testAfterPropertiesSetForQueryString() { + reader = new MongoCursorItemReader<>(); + Exception exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); + assertEquals("An implementation of MongoOperations is required.", exception.getMessage()); - reader.setTargetType(String.class); + reader.setTemplate(template); - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A query is required.", exception.getMessage()); + exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); + assertEquals("A targetType to convert the input into is required.", exception.getMessage()); - reader.setQuery(""); + reader.setTargetType(String.class); - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A sort is required.", exception.getMessage()); + exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); + assertEquals("A query is required.", exception.getMessage()); - reader.setSort(sortOptions); - reader.afterPropertiesSet(); - } + reader.setQuery(""); - @Test - void testAfterPropertiesSetForQueryObject() { - reader = new MongoCursorItemReader<>(); + exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); + assertEquals("A sort is required.", exception.getMessage()); - reader.setTemplate(template); - reader.setTargetType(String.class); + reader.setSort(sortOptions); + reader.afterPropertiesSet(); + } - Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); - reader.setQuery(query); + @Test + void testAfterPropertiesSetForQueryObject() { + reader = new MongoCursorItemReader<>(); - reader.afterPropertiesSet(); - } + reader.setTemplate(template); + reader.setTargetType(String.class); - @Test - void testBasicQuery() throws Exception { - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); + reader.setQuery(query); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of("hello world")); + reader.afterPropertiesSet(); + } - reader.doOpen(); - assertEquals(reader.doRead(), "hello world"); + @Test + void testBasicQuery() throws Exception { + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of("hello world")); - @Test - void testQueryWithFields() throws Exception { - reader.setFields("{name : 1, age : 1, _id: 0}"); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + reader.doOpen(); + assertEquals(reader.doRead(), "hello world"); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + } - reader.doOpen(); - assertNull(reader.doRead()); + @Test + void testQueryWithFields() throws Exception { + reader.setFields("{name : 1, age : 1, _id: 0}"); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals(1, query.getFieldsObject().get("name")); - assertEquals(1, query.getFieldsObject().get("age")); - assertEquals(0, query.getFieldsObject().get("_id")); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - @Test - void testQueryWithHint() throws Exception { - reader.setHint("{ $natural : 1}"); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + reader.doOpen(); + assertNull(reader.doRead()); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals(1, query.getFieldsObject().get("name")); + assertEquals(1, query.getFieldsObject().get("age")); + assertEquals(0, query.getFieldsObject().get("_id")); + } - reader.doOpen(); - assertNull(reader.doRead()); + @Test + void testQueryWithHint() throws Exception { + reader.setHint("{ $natural : 1}"); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals("{ $natural : 1}", query.getHint()); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - @Test - void testQueryWithParameters() throws Exception { - reader.setParameterValues(Collections.singletonList("foo")); + reader.doOpen(); + assertNull(reader.doRead()); - reader.setQuery("{ name : ?0 }"); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals("{ $natural : 1}", query.getHint()); + } - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + @Test + void testQueryWithParameters() throws Exception { + reader.setParameterValues(Collections.singletonList("foo")); - reader.doOpen(); - assertNull(reader.doRead()); + reader.setQuery("{ name : ?0 }"); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{\"name\": \"foo\"}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - @Test - void testQueryWithBatchSize() throws Exception { - reader.setBatchSize(50); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + reader.doOpen(); + assertNull(reader.doRead()); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + Query query = queryContainer.getValue(); + assertEquals("{\"name\": \"foo\"}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + } - reader.doOpen(); - assertNull(reader.doRead()); + @Test + void testQueryWithBatchSize() throws Exception { + reader.setBatchSize(50); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals(50, query.getMeta().getCursorBatchSize()); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - @Test - void testQueryWithLimit() throws Exception { - reader.setLimit(200); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + reader.doOpen(); + assertNull(reader.doRead()); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals(50, query.getMeta().getCursorBatchSize()); + } - reader.doOpen(); - assertNull(reader.doRead()); + @Test + void testQueryWithLimit() throws Exception { + reader.setLimit(200); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals(200, query.getLimit()); - } + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - @Test - void testQueryWithMaxTime() throws Exception { - reader.setMaxTimeMs(3000); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + reader.doOpen(); + assertNull(reader.doRead()); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals(200, query.getLimit()); + } - reader.doOpen(); - assertNull(reader.doRead()); - - Query query = queryContainer.getValue(); - assertEquals("{}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals(3000, query.getMeta().getMaxTimeMsec()); - } - - @Test - void testQueryWithCollection() throws Exception { - reader.setParameterValues(Collections.singletonList("foo")); - - reader.setQuery("{ name : ?0 }"); - reader.setCollection("collection"); - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - ArgumentCaptor collectionContainer = ArgumentCaptor.forClass(String.class); - - when(template.stream(queryContainer.capture(), eq(String.class), collectionContainer.capture())) - .thenReturn(Stream.of()); - - reader.doOpen(); - assertNull(reader.doRead()); - - Query query = queryContainer.getValue(); - assertEquals("{\"name\": \"foo\"}", query.getQueryObject().toJson()); - assertEquals("{\"name\": -1}", query.getSortObject().toJson()); - assertEquals("collection", collectionContainer.getValue()); - } + @Test + void testQueryWithMaxTime() throws Exception { + reader.setMaxTime(Duration.ofMillis(3000)); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - @Test - void testQueryObject() throws Exception { - reader = new MongoCursorItemReader<>(); - reader.setTemplate(template); + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); - reader.setQuery(query); - reader.setTargetType(String.class); - - reader.afterPropertiesSet(); - - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); + reader.doOpen(); + assertNull(reader.doRead()); + + Query query = queryContainer.getValue(); + assertEquals("{}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals(3000, query.getMeta().getMaxTimeMsec()); + } + + @Test + void testQueryWithCollection() throws Exception { + reader.setParameterValues(Collections.singletonList("foo")); + + reader.setQuery("{ name : ?0 }"); + reader.setCollection("collection"); + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + ArgumentCaptor collectionContainer = ArgumentCaptor.forClass(String.class); + + when(template.stream(queryContainer.capture(), eq(String.class), collectionContainer.capture())) + .thenReturn(Stream.of()); + + reader.doOpen(); + assertNull(reader.doRead()); + + Query query = queryContainer.getValue(); + assertEquals("{\"name\": \"foo\"}", query.getQueryObject().toJson()); + assertEquals("{\"name\": -1}", query.getSortObject().toJson()); + assertEquals("collection", collectionContainer.getValue()); + } + + @Test + void testQueryObject() throws Exception { + reader = new MongoCursorItemReader<>(); + reader.setTemplate(template); - reader.doOpen(); - assertNull(reader.doRead()); + Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); + reader.setQuery(query); + reader.setTargetType(String.class); + + reader.afterPropertiesSet(); + + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); - Query actualQuery = queryContainer.getValue(); - assertEquals("{}", actualQuery.getQueryObject().toJson()); - assertEquals("{\"_id\": 1}", actualQuery.getSortObject().toJson()); - } - - @Test - void testQueryObjectWithCollection() throws Exception { - reader = new MongoCursorItemReader<>(); - reader.setTemplate(template); - - Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); - reader.setQuery(query); - reader.setTargetType(String.class); - reader.setCollection("collection"); - - reader.afterPropertiesSet(); - - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - ArgumentCaptor stringContainer = ArgumentCaptor.forClass(String.class); - when(template.stream(queryContainer.capture(), eq(String.class), stringContainer.capture())).thenReturn(Stream.of()); - - reader.doOpen(); - assertNull(reader.doRead()); - - Query actualQuery = queryContainer.getValue(); - assertEquals("{}", actualQuery.getQueryObject().toJson()); - assertEquals("{\"_id\": 1}", actualQuery.getSortObject().toJson()); - assertEquals("collection", stringContainer.getValue()); - } - - @Test - void testSortThrowsExceptionWhenInvokedWithNull() { - // given - reader = new MongoCursorItemReader<>(); + reader.doOpen(); + assertNull(reader.doRead()); - // when + then - assertThatIllegalArgumentException().isThrownBy(() -> reader.setSort(null)) - .withMessage("Sorts must not be null"); - } - - @Test - void testCursorRead() throws Exception { - ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); - when(template.stream(queryContainer.capture(), eq(String.class))) - .thenReturn(Stream.of("first", "second", "third")); - - reader.doOpen(); - - assertEquals("first", reader.doRead()); - assertEquals("second", reader.doRead()); - assertEquals("third", reader.doRead()); - assertNull(reader.doRead()); - } + Query actualQuery = queryContainer.getValue(); + assertEquals("{}", actualQuery.getQueryObject().toJson()); + assertEquals("{\"_id\": 1}", actualQuery.getSortObject().toJson()); + } + + @Test + void testQueryObjectWithCollection() throws Exception { + reader = new MongoCursorItemReader<>(); + reader.setTemplate(template); + + Query query = new Query().with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id"))); + reader.setQuery(query); + reader.setTargetType(String.class); + reader.setCollection("collection"); + + reader.afterPropertiesSet(); + + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + ArgumentCaptor stringContainer = ArgumentCaptor.forClass(String.class); + when(template.stream(queryContainer.capture(), eq(String.class), stringContainer.capture())) + .thenReturn(Stream.of()); + + reader.doOpen(); + assertNull(reader.doRead()); + + Query actualQuery = queryContainer.getValue(); + assertEquals("{}", actualQuery.getQueryObject().toJson()); + assertEquals("{\"_id\": 1}", actualQuery.getSortObject().toJson()); + assertEquals("collection", stringContainer.getValue()); + } + + @Test + void testSortThrowsExceptionWhenInvokedWithNull() { + // given + reader = new MongoCursorItemReader<>(); + + // when + then + assertThatIllegalArgumentException().isThrownBy(() -> reader.setSort(null)) + .withMessage("Sorts must not be null"); + } + + @Test + void testCursorRead() throws Exception { + ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); + when(template.stream(queryContainer.capture(), eq(String.class))) + .thenReturn(Stream.of("first", "second", "third")); + + reader.doOpen(); + + assertEquals("first", reader.doRead()); + assertEquals("second", reader.doRead()); + assertEquals("third", reader.doRead()); + assertNull(reader.doRead()); + } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java new file mode 100644 index 0000000000..f8dcfa6e32 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 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 + * + * https://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.item.data.builder; + +import java.time.Duration; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.data.MongoCursorItemReader; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.mock; + +/** + * Test class for {@link MongoCursorItemReaderBuilder}. + * + * @author Mahmoud Ben Hassine + */ +public class MongoCursorItemReaderBuilderTests { + + @Test + void testBuild() { + // given + MongoTemplate template = mock(); + Class targetType = String.class; + Query query = mock(); + Map sorts = mock(); + int batchSize = 100; + int limit = 10000; + Duration maxTime = Duration.ofMillis(1000); + + // when + MongoCursorItemReader reader = new MongoCursorItemReaderBuilder().name("reader") + .template(template) + .targetType(targetType) + .query(query) + .sorts(sorts) + .batchSize(batchSize) + .limit(limit) + .maxTime(maxTime) + .build(); + + // then + Assertions.assertEquals(template, ReflectionTestUtils.getField(reader, "template")); + Assertions.assertEquals(targetType, ReflectionTestUtils.getField(reader, "targetType")); + Assertions.assertEquals(query, ReflectionTestUtils.getField(reader, "query")); + Assertions.assertEquals(batchSize, ReflectionTestUtils.getField(reader, "batchSize")); + Assertions.assertEquals(limit, ReflectionTestUtils.getField(reader, "limit")); + Assertions.assertEquals(maxTime, ReflectionTestUtils.getField(reader, "maxTime")); + } + +}