/* * Copyright 2015 MongoDB, Inc. * * 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 * * 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 com.mongodb.client.model; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; import org.bson.BsonInt32; import org.bson.BsonInt64; import org.bson.BsonString; import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import java.util.List; import java.util.Map; import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.client.model.BuildersHelper.encodeValue; import static java.util.Arrays.asList; /** * A factory for document updates. A convenient way to use this class is to statically import all of its methods, which allows usage like: * <blockquote><pre> * collection.updateOne(eq("x", 1), set("x", 2)); * </pre></blockquote> * * @since 3.1 * @mongodb.driver.manual reference/operator/update/ Update Operators */ public final class Updates { /** * Combine a list of updates into a single update. * * @param updates the list of updates * @return a combined update */ public static Bson combine(final Bson... updates) { return combine(asList(updates)); } /** * Combine a list of updates into a single update. * * @param updates the list of updates * @return a combined update */ public static Bson combine(final List<? extends Bson> updates) { notNull("updates", updates); return new CompositeUpdate(updates); } /** * Creates an update that sets the value of the field with the given name to the given value. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/set/ $set */ public static <TItem> Bson set(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$set"); } /** * Creates an update that deletes the field with the given name. * * @param fieldName the non-null field name * @return the update * @mongodb.driver.manual reference/operator/update/unset/ $unset */ public static Bson unset(final String fieldName) { return new SimpleUpdate<String>(fieldName, "", "$unset"); } /** * Creates an update that sets the value of the field with the given name to the given value, but only if the update is an upsert that * results in an insert of a document. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/setOnInsert/ $setOnInsert * @see UpdateOptions#upsert(boolean) */ public static <TItem> Bson setOnInsert(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$setOnInsert"); } /** * Creates an update that renames a field. * * @param fieldName the non-null field name * @param newFieldName the non-null new field name * @return the update * @mongodb.driver.manual reference/operator/update/rename/ $rename */ public static Bson rename(final String fieldName, final String newFieldName) { notNull("newFieldName", newFieldName); return new SimpleUpdate<String>(fieldName, newFieldName, "$rename"); } /** * Creates an update that increments the value of the field with the given name by the given value. * * @param fieldName the non-null field name * @param number the value * @return the update * @mongodb.driver.manual reference/operator/update/inc/ $inc */ public static Bson inc(final String fieldName, final Number number) { notNull("number", number); return new SimpleUpdate<Number>(fieldName, number, "$inc"); } /** * Creates an update that multiplies the value of the field with the given name by the given number. * * @param fieldName the non-null field name * @param number the non-null number * @return the update * @mongodb.driver.manual reference/operator/update/mul/ $mul */ public static Bson mul(final String fieldName, final Number number) { notNull("number", number); return new SimpleUpdate<Number>(fieldName, number, "$mul"); } /** * Creates an update that sets the value of the field to the given value if the given value is less than the current value of the * field. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/min/ $min */ public static <TItem> Bson min(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$min"); } /** * Creates an update that sets the value of the field to the given value if the given value is greater than the current value of the * field. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/min/ $min */ public static <TItem> Bson max(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$max"); } /** * Creates an update that sets the value of the field to the current date as a BSON date. * * @param fieldName the non-null field name * @return the update * @mongodb.driver.manual reference/operator/update/currentDate/ $currentDate * @mongodb.driver.manual reference/bson-types/#date Date */ public static Bson currentDate(final String fieldName) { return new SimpleUpdate<Boolean>(fieldName, true, "$currentDate"); } /** * Creates an update that sets the value of the field to the current date as a BSON timestamp. * * @param fieldName the non-null field name * @return the update * @mongodb.driver.manual reference/operator/update/currentDate/ $currentDate * @mongodb.driver.manual reference/bson-types/#document-bson-type-timestamp Timestamp */ public static Bson currentTimestamp(final String fieldName) { return new SimpleUpdate<BsonDocument>(fieldName, new BsonDocument("$type", new BsonString("timestamp")), "$currentDate"); } /** * Creates an update that adds the given value to the array value of the field with the given name, unless the value is * already present, in which case it does nothing * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/addToSet/ $addToSet */ public static <TItem> Bson addToSet(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$addToSet"); } /** * Creates an update that adds each of the given values to the array value of the field with the given name, unless the value is * already present, in which case it does nothing * * @param fieldName the non-null field name * @param values the values * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/addToSet/ $addToSet */ public static <TItem> Bson addEachToSet(final String fieldName, final List<TItem> values) { return new WithEachUpdate<TItem>(fieldName, values, "$addToSet"); } /** * Creates an update that adds the given value to the array value of the field with the given name. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/push/ $push */ public static <TItem> Bson push(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$push"); } /** * Creates an update that adds each of the given values to the array value of the field with the given name. * * @param fieldName the non-null field name * @param values the values * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/push/ $push */ public static <TItem> Bson pushEach(final String fieldName, final List<TItem> values) { return new PushUpdate<TItem>(fieldName, values, new PushOptions()); } /** * Creates an update that adds each of the given values to the array value of the field with the given name, applying the given * options for positioning the pushed values, and then slicing and/or sorting the array. * * @param fieldName the non-null field name * @param values the values * @param options the non-null push options * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/push/ $push */ public static <TItem> Bson pushEach(final String fieldName, final List<TItem> values, final PushOptions options) { return new PushUpdate<TItem>(fieldName, values, options); } /** * Creates an update that removes all instances of the given value from the array value of the field with the given name. * * @param fieldName the non-null field name * @param value the value * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/pull/ $pull */ public static <TItem> Bson pull(final String fieldName, final TItem value) { return new SimpleUpdate<TItem>(fieldName, value, "$pull"); } /** * Creates an update that removes from an array all elements that match the given filter. * * @param filter the query filter * @return the update * @mongodb.driver.manual reference/operator/update/pull/ $pull */ public static Bson pullByFilter(final Bson filter) { return new Bson() { @Override public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { BsonDocumentWriter writer = new BsonDocumentWriter(new BsonDocument()); writer.writeStartDocument(); writer.writeName("$pull"); encodeValue(writer, filter, codecRegistry); writer.writeEndDocument(); return writer.getDocument(); } }; } /** * Creates an update that removes all instances of the given values from the array value of the field with the given name. * * @param fieldName the non-null field name * @param values the values * @param <TItem> the value type * @return the update * @mongodb.driver.manual reference/operator/update/pull/ $pull */ public static <TItem> Bson pullAll(final String fieldName, final List<TItem> values) { return new PullAllUpdate<TItem>(fieldName, values); } /** * Creates an update that pops the first element of an array that is the value of the field with the given name. * * @param fieldName the non-null field name * @return the update * @mongodb.driver.manual reference/operator/update/pop/ $pop */ public static Bson popFirst(final String fieldName) { return new SimpleUpdate<Integer>(fieldName, -1, "$pop"); } /** * Creates an update that pops the last element of an array that is the value of the field with the given name. * * @param fieldName the non-null field name * @return the update * @mongodb.driver.manual reference/operator/update/pop/ $pop */ public static Bson popLast(final String fieldName) { return new SimpleUpdate<Integer>(fieldName, 1, "$pop"); } /** * Creates an update that performs a bitwise and between the given integer value and the integral value of the field with the given * name. * * @param fieldName the field name * @param value the value * @return the update */ public static Bson bitwiseAnd(final String fieldName, final int value) { return createBitUpdateDocument(fieldName, "and", value); } /** * Creates an update that performs a bitwise and between the given long value and the integral value of the field with the given name. * * @param fieldName the field name * @param value the value * @return the update * @mongodb.driver.manual reference/operator/update/bit/ $bit */ public static Bson bitwiseAnd(final String fieldName, final long value) { return createBitUpdateDocument(fieldName, "and", value); } /** * Creates an update that performs a bitwise or between the given integer value and the integral value of the field with the given * name. * * @param fieldName the field name * @param value the value * @return the update * @mongodb.driver.manual reference/operator/update/bit/ $bit */ public static Bson bitwiseOr(final String fieldName, final int value) { return createBitUpdateDocument(fieldName, "or", value); } /** * Creates an update that performs a bitwise or between the given long value and the integral value of the field with the given name. * * @param fieldName the field name * @param value the value * @return the update * @mongodb.driver.manual reference/operator/update/bit/ $bit */ public static Bson bitwiseOr(final String fieldName, final long value) { return createBitUpdateDocument(fieldName, "or", value); } /** * Creates an update that performs a bitwise xor between the given integer value and the integral value of the field with the given * name. * * @param fieldName the field name * @param value the value * @return the update */ public static Bson bitwiseXor(final String fieldName, final int value) { return createBitUpdateDocument(fieldName, "xor", value); } /** * Creates an update that performs a bitwise xor between the given long value and the integral value of the field with the given name. * * @param fieldName the field name * @param value the value * @return the update */ public static Bson bitwiseXor(final String fieldName, final long value) { return createBitUpdateDocument(fieldName, "xor", value); } private static Bson createBitUpdateDocument(final String fieldName, final String bitwiseOperator, final int value) { return createBitUpdateDocument(fieldName, bitwiseOperator, new BsonInt32(value)); } private static Bson createBitUpdateDocument(final String fieldName, final String bitwiseOperator, final long value) { return createBitUpdateDocument(fieldName, bitwiseOperator, new BsonInt64(value)); } private static Bson createBitUpdateDocument(final String fieldName, final String bitwiseOperator, final BsonValue value) { return new BsonDocument("$bit", new BsonDocument(fieldName, new BsonDocument(bitwiseOperator, value))); } private static class SimpleUpdate<TItem> implements Bson { private final String fieldName; private final TItem value; private final String operator; SimpleUpdate(final String fieldName, final TItem value, final String operator) { this.fieldName = notNull("fieldName", fieldName); this.value = value; this.operator = operator; } @Override public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { BsonDocumentWriter writer = new BsonDocumentWriter(new BsonDocument()); writer.writeStartDocument(); writer.writeName(operator); writer.writeStartDocument(); writer.writeName(fieldName); encodeValue(writer, value, codecRegistry); writer.writeEndDocument(); writer.writeEndDocument(); return writer.getDocument(); } @Override public String toString() { return "Update{" + "fieldName='" + fieldName + '\'' + ", operator='" + operator + '\'' + ", value=" + value + '}'; } } private static class WithEachUpdate<TItem> implements Bson { private final String fieldName; private final List<TItem> values; private final String operator; WithEachUpdate(final String fieldName, final List<TItem> values, final String operator) { this.fieldName = notNull("fieldName", fieldName); this.values = notNull("values", values); this.operator = operator; } @Override public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { BsonDocumentWriter writer = new BsonDocumentWriter(new BsonDocument()); writer.writeStartDocument(); writer.writeName(operator); writer.writeStartDocument(); writer.writeName(fieldName); writer.writeStartDocument(); writer.writeStartArray("$each"); for (TItem value : values) { encodeValue(writer, value, codecRegistry); } writer.writeEndArray(); writeAdditionalFields(writer, tDocumentClass, codecRegistry); writer.writeEndDocument(); writer.writeEndDocument(); writer.writeEndDocument(); return writer.getDocument(); } protected <TDocument> void writeAdditionalFields(final BsonDocumentWriter writer, final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { } protected String additionalFieldsToString() { return ""; } @Override public String toString() { return "Each Update{" + "fieldName='" + fieldName + '\'' + ", operator='" + operator + '\'' + ", values=" + values + additionalFieldsToString() + '}'; } } private static class PushUpdate<TItem> extends WithEachUpdate<TItem> { private final PushOptions options; PushUpdate(final String fieldName, final List<TItem> values, final PushOptions options) { super(fieldName, values, "$push"); this.options = notNull("options", options); } @Override protected <TDocument> void writeAdditionalFields(final BsonDocumentWriter writer, final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { if (options.getPosition() != null) { writer.writeInt32("$position", options.getPosition()); } if (options.getSlice() != null) { writer.writeInt32("$slice", options.getSlice()); } if (options.getSort() != null) { writer.writeInt32("$sort", options.getSort()); } else if (options.getSortDocument() != null) { writer.writeName("$sort"); encodeValue(writer, options.getSortDocument(), codecRegistry); } } @Override protected String additionalFieldsToString() { return ", options=" + options; } } private static class PullAllUpdate<TItem> implements Bson { private final String fieldName; private final List<TItem> values; PullAllUpdate(final String fieldName, final List<TItem> values) { this.fieldName = notNull("fieldName", fieldName); this.values = notNull("values", values); } @Override public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { BsonDocumentWriter writer = new BsonDocumentWriter(new BsonDocument()); writer.writeStartDocument(); writer.writeName("$pullAll"); writer.writeStartDocument(); writer.writeName(fieldName); writer.writeStartArray(); for (TItem value : values) { encodeValue(writer, value, codecRegistry); } writer.writeEndArray(); writer.writeEndDocument(); writer.writeEndDocument(); return writer.getDocument(); } @Override public String toString() { return "Update{" + "fieldName='" + fieldName + '\'' + ", operator='$pullAll'" + ", value=" + values + '}'; } } private static class CompositeUpdate implements Bson { private final List<? extends Bson> updates; CompositeUpdate(final List<? extends Bson> updates) { this.updates = updates; } @Override public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentClass, final CodecRegistry codecRegistry) { BsonDocument document = new BsonDocument(); for (Bson update : updates) { BsonDocument rendered = update.toBsonDocument(tDocumentClass, codecRegistry); for (Map.Entry<String, BsonValue> element : rendered.entrySet()) { if (document.containsKey(element.getKey())) { BsonDocument currentOperatorDocument = (BsonDocument) element.getValue(); BsonDocument existingOperatorDocument = document.getDocument(element.getKey()); for (Map.Entry<String, BsonValue> currentOperationDocumentElements : currentOperatorDocument.entrySet()) { existingOperatorDocument.append(currentOperationDocumentElements.getKey(), currentOperationDocumentElements.getValue()); } } else { document.append(element.getKey(), element.getValue()); } } } return document; } @Override public String toString() { return "Updates{" + "updates=" + updates + '}'; } } private Updates() { } }