MapReduceOptions.java

/*
 * Copyright 2010-present 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.data.mongodb.core.mapreduce;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.bson.Document;
import org.jspecify.annotations.Nullable;
import org.springframework.data.mongodb.core.query.Collation;

import com.mongodb.client.model.MapReduceAction;
import org.springframework.lang.Contract;

/**
 * @author Mark Pollack
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @author Mark Paluch
 * @deprecated since 3.4 in favor of {@link org.springframework.data.mongodb.core.aggregation}.
 */
@Deprecated
public class MapReduceOptions {

	private @Nullable String outputCollection;

	private Optional<String> outputDatabase = Optional.empty();
	private @Nullable MapReduceAction mapReduceAction = MapReduceAction.REPLACE;
	private Map<String, Object> scopeVariables = new HashMap<>();
	private Map<String, Object> extraOptions = new HashMap<>();
	private @Nullable Boolean jsMode;
	private Boolean verbose = Boolean.TRUE;
	private @Nullable Integer limit;

	private Optional<String> finalizeFunction = Optional.empty();
	private Optional<Collation> collation = Optional.empty();

	/**
	 * Static factory method to create a MapReduceOptions instance
	 *
	 * @return a new instance
	 */
	public static MapReduceOptions options() {
		return new MapReduceOptions();
	}

	/**
	 * Limit the number of objects to return from the collection that is fed into the map reduce operation Often used in
	 * conjunction with a query and sort option so as to reduce the portion of the data that will be processed.
	 *
	 * @param limit Limit the number of objects to process
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions limit(int limit) {

		this.limit = limit;
		return this;
	}

	/**
	 * The collection where the results from the map-reduce operation will be stored. Note, you can set the database name
	 * as well with the outputDatabase option.
	 *
	 * @param collectionName The name of the collection where the results of the map-reduce operation will be stored.
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions outputCollection(String collectionName) {

		this.outputCollection = collectionName;
		return this;
	}

	/**
	 * The database where the results from the map-reduce operation will be stored. Note, you ca set the collection name
	 * as well with the outputCollection option.
	 *
	 * @param outputDatabase The name of the database where the results of the map-reduce operation will be stored.
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions outputDatabase(@Nullable String outputDatabase) {

		this.outputDatabase = Optional.ofNullable(outputDatabase);
		return this;
	}

	/**
	 * With this option, no collection will be created, and the whole map-reduce operation will happen in RAM. Also, the
	 * results of the map-reduce will be returned within the result object. Note that this option is possible only when
	 * the result set fits within the 16MB limit of a single document.
	 *
	 * @return this.
	 * @since 3.0
	 */
	@Contract("-> this")
	public MapReduceOptions actionInline() {

		this.mapReduceAction = null;
		return this;
	}


	/**
	 * This option will merge new data into the old output collection. In other words, if the same key exists in both the
	 * result set and the old collection, the new key will overwrite the old one.
	 *
	 * @return this.
	 * @since 3.0
	 */
	@Contract("-> this")
	public MapReduceOptions actionMerge() {

		this.mapReduceAction = MapReduceAction.MERGE;
		return this;
	}

	/**
	 * If documents exists for a given key in the result set and in the old collection, then a reduce operation (using the
	 * specified reduce function) will be performed on the two values and the result will be written to the output
	 * collection. If a finalize function was provided, this will be run after the reduce as well.
	 *
	 * @return this.
	 * @since 3.0
	 */
	@Contract("-> this")
	public MapReduceOptions actionReduce() {

		this.mapReduceAction = MapReduceAction.REDUCE;
		return this;
	}

	/**
	 * The output will be inserted into a collection which will atomically replace any existing collection with the same
	 * name. Note, the default is {@link MapReduceAction#REPLACE}.
	 *
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 * @since 3.0
	 */
	@Contract("-> this")
	public MapReduceOptions actionReplace() {

		this.mapReduceAction = MapReduceAction.REPLACE;
		return this;
	}

	/**
	 * Sets the finalize function
	 *
	 * @param finalizeFunction The finalize function. Can be a JSON string or a Spring Resource URL
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions finalizeFunction(@Nullable String finalizeFunction) {

		this.finalizeFunction = Optional.ofNullable(finalizeFunction);
		return this;
	}

	/**
	 * Key-value pairs that are placed into JavaScript global scope and can be accessed from map, reduce, and finalize
	 * scripts.
	 *
	 * @param scopeVariables variables that can be accessed from map, reduce, and finalize scripts
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions scopeVariables(Map<String, Object> scopeVariables) {

		this.scopeVariables = scopeVariables;
		return this;
	}

	/**
	 * Flag that toggles behavior in the map-reduce operation so as to avoid intermediate conversion to BSON between the
	 * map and reduce steps. For MongoDB 1.9+
	 *
	 * @param javaScriptMode if true, have the execution of map-reduce stay in JavaScript
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions javaScriptMode(boolean javaScriptMode) {

		this.jsMode = javaScriptMode;
		return this;
	}

	/**
	 * Flag to set that will provide statistics on job execution time.
	 *
	 * @return MapReduceOptions so that methods can be chained in a fluent API style
	 */
	@Contract("_ -> this")
	public MapReduceOptions verbose(boolean verbose) {

		this.verbose = verbose;
		return this;
	}

	/**
	 * Define the Collation specifying language-specific rules for string comparison.
	 *
	 * @param collation can be {@literal null}.
	 * @return
	 * @since 2.0
	 */
	@Contract("_ -> this")
	public MapReduceOptions collation(@Nullable Collation collation) {

		this.collation = Optional.ofNullable(collation);
		return this;
	}

	public Optional<String> getFinalizeFunction() {
		return this.finalizeFunction;
	}

	public @Nullable Boolean getJavaScriptMode() {
		return this.jsMode;
	}

	public @Nullable String getOutputCollection() {
		return this.outputCollection;
	}

	public Optional<String> getOutputDatabase() {
		return this.outputDatabase;
	}

	public Map<String, Object> getScopeVariables() {
		return this.scopeVariables;
	}

	/**
	 * Get the maximum number of documents for the input into the map function.
	 *
	 * @return {@literal null} if not set.
	 */
	@Nullable
	public Integer getLimit() {
		return limit;
	}

	/**
	 * Get the Collation specifying language-specific rules for string comparison.
	 *
	 * @return
	 * @since 2.0
	 */
	public Optional<Collation> getCollation() {
		return collation;
	}

	/**
	 * Return the {@link MapReduceAction}.
	 *
	 * @return the mapped action or {@literal null} if the action maps to inline output.
	 * @since 2.0.10
	 */
	public @Nullable MapReduceAction getMapReduceAction() {
		return mapReduceAction;
	}

	/**
	 * @return {@literal true} if {@literal inline} output is used.
	 * @since 2.0.10
	 */
	public boolean usesInlineOutput() {
		return null == mapReduceAction;
	}

	public Document getOptionsObject() {

		Document cmd = new Document();

		if (verbose != null) {
			cmd.put("verbose", verbose);
		}

		cmd.put("out", createOutObject());

		finalizeFunction.ifPresent(val -> cmd.append("finalize", val));

		if (scopeVariables != null) {
			cmd.put("scope", scopeVariables);
		}

		if (limit != null) {
			cmd.put("limit", limit);
		}

		if (!extraOptions.keySet().isEmpty()) {
			cmd.putAll(extraOptions);
		}

		getCollation().ifPresent(val -> cmd.append("collation", val.toDocument()));

		return cmd;
	}

	protected Document createOutObject() {

		Document out = new Document();

		if (getMapReduceAction() == null) {
			out.put("inline", 1);
		} else {
			switch (getMapReduceAction()) {
				case REPLACE -> out.put("replace", outputCollection);
				case MERGE -> out.put("merge", outputCollection);
				case REDUCE -> out.put("reduce", outputCollection);
			}
		}

		outputDatabase.ifPresent(val -> out.append("db", val));

		return out;
	}
}