com.tomtom.speedtools.mongodb.MongoDBUpdate.java Source code

Java tutorial

Introduction

Here is the source code for com.tomtom.speedtools.mongodb.MongoDBUpdate.java

Source

/*
 * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com).
 *
 * 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
 *
 *    http://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 com.tomtom.speedtools.mongodb;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.tomtom.speedtools.mongodb.mappers.*;
import com.tomtom.speedtools.mongodb.mappers.EntityMapper.HasFieldName;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

import static com.tomtom.speedtools.mongodb.MongoDBUtils.mongoPath;

/**
 * Utility to facilitate creating mongo update objects using mapper fields. Fields in nested objects can be addressed.
 * Query values are type-safe.
 *
 * Example:
 * <pre>
 * collection.update(
 *   new MongoDBQuery().
 *     eq(personMapper.lastName, "Henderson"),
 *   new MongoDBUpdate().
 *     set(personMapper.address.street, "Sunset blvd."));
 * </pre>
 */
public class MongoDBUpdate {

    public static final String OPERATOR_SET = "$set";
    public static final String OPERATOR_UNSET = "$unset";
    public static final String OPERATOR_PUSH = "$push";
    public static final String OPERATOR_PUSH_ALL = "$pushAll";
    public static final String OPERATOR_PULL = "$pull";
    public static final String OPERATOR_POP_FIRST = "$popFirst";
    public static final String OPERATOR_POP_LAST = "$popLast";
    public static final String OPERATOR_INC = "$inc";
    public static final String OPERATOR_ADD_TO_SET = "$addToSet";

    @Nonnull
    private final List<Modifier> modifiers = new LinkedList<>();

    @Nonnull
    public <T> MongoDBUpdate set(@Nonnull final EntityMapper<?>.Field<T> f0, @Nullable final T value) {
        assert f0 != null;
        return addModifier(OPERATOR_SET, mongoPath(f0), f0.getMapper(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate set(@Nonnull final HasFieldName f0, @Nonnull final EntityMapper<?>.Field<T> f1,
            @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        return addModifier(OPERATOR_SET, mongoPath(f0, f1), f1.getMapper(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate set(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<T> f2, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_SET, mongoPath(f0, f1, f2), f2.getMapper(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate set(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<T> f3, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_SET, mongoPath(f0, f1, f2, f3), f3.getMapper(), value);
    }

    @Nonnull
    public <E> MongoDBUpdate setAllExcept(@Nonnull final EntityMapper<E> mapper, @Nonnull final E entity,
            @Nonnull final EntityMapper<E>.Field<?>... fields) throws MapperException {
        assert mapper != null;
        assert entity != null;
        assert fields != null;
        final Set<EntityMapper<E>.Field<?>> fieldSet = new HashSet<>(Arrays.asList(fields));

        // Get all fields from the sub mapper.
        for (final EntityMapper<E>.Field<?> field : mapper.getSubMapper(entity.getClass())
                .getFieldsIncludingSuperEntities()) {
            if (!fieldSet.contains(field)) {
                // ID field must be excluded.
                if (!field.getFieldName().equals(MongoDBKeyNames.ID_KEY)) {
                    final DBObject dbObject = mapper.toDb(entity);
                    // Since entity is not null a dbObject is expected.
                    assert dbObject != null;
                    final Object value = dbObject.get(field.getFieldName());

                    if (value != null) {
                        modifiers.add(new Modifier(OPERATOR_SET, mongoPath(field)) {
                            @Nullable
                            @Override
                            Object toDBValue() {
                                return value;
                            }
                        });
                    } else {
                        // When a value is null it has to be unset since the policy is to not store null values.
                        modifiers.add(new Modifier(OPERATOR_UNSET, mongoPath(field)) {
                            @Nullable
                            @Override
                            Object toDBValue() {
                                return 1;
                            }
                        });
                    }
                }
            }
        }
        return this;
    }

    @Nonnull
    public <T> MongoDBUpdate setRaw(@Nonnull final String fieldName, @Nullable final T value) {
        assert fieldName != null;
        return addModifier(OPERATOR_SET, fieldName, IdentityMapper.getInstance(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate setRaw(@Nonnull final String fieldName, @Nonnull final Mapper<T> mapper,
            @Nullable final T value) {
        assert fieldName != null;
        assert mapper != null;
        return addModifier(OPERATOR_SET, fieldName, mapper, value);
    }

    @Nonnull
    public MongoDBUpdate unset(@Nonnull final EntityMapper<?>.Field<?>... fs) {
        assert fs != null;
        assert fs.length > 0;
        return addModifier(OPERATOR_UNSET, mongoPath(fs), IdentityMapper.getInstance(), 1);
    }

    @Nonnull
    public <T> MongoDBUpdate push(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0, @Nullable final T value) {
        assert f0 != null;
        return addModifier(OPERATOR_PUSH, mongoPath(f0), getElementMapper(f0.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate push(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert value != null;
        return addModifier(OPERATOR_PUSH, mongoPath(f0, f1), getElementMapper(f1.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate push(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_PUSH, mongoPath(f0, f1, f2), getElementMapper(f2.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate push(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3,
            @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_PUSH, mongoPath(f0, f1, f2, f3), getElementMapper(f3.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate pushAll(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0,
            final Collection<T> values) {
        assert f0 != null;
        assert values != null;
        return addModifier(OPERATOR_PUSH_ALL, mongoPath(f0), f0.getMapper(), values);
    }

    @Nonnull
    public <T> MongoDBUpdate pushAll(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1, final Collection<T> values) {
        assert f0 != null;
        assert f1 != null;
        assert values != null;
        return addModifier(OPERATOR_PUSH_ALL, mongoPath(f0, f1), f1.getMapper(), values);
    }

    @Nonnull
    public <T> MongoDBUpdate pushAll(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2, final Collection<T> values) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert values != null;
        return addModifier(OPERATOR_PUSH_ALL, mongoPath(f0, f1, f2), f2.getMapper(), values);
    }

    @Nonnull
    public <T> MongoDBUpdate pushAll(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3,
            final Collection<T> values) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_PUSH_ALL, mongoPath(f0, f1, f2, f3), f3.getMapper(), values);
    }

    @Nonnull
    public <T> MongoDBUpdate pull(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0, @Nullable final T value) {
        assert f0 != null;
        return addModifier(OPERATOR_PULL, mongoPath(f0), getElementMapper(f0.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate pull(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        return addModifier(OPERATOR_PULL, mongoPath(f0, f1), getElementMapper(f1.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate pull(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_PULL, mongoPath(f0, f1, f2), getElementMapper(f2.getMapper()), value);
    }

    /**
     * Removes all occurrences of value from a field, if field is an array. If the field is present but is not an array,
     * an error condition is raised.
     *
     * Mongo shell equivalent:
     * <pre>
     * { $pull : { field : {field2: value} } }
     * </pre>
     *
     * @param <T>          Type.
     * @param f0           The field that holds an array.
     * @param mongoDBQuery The query that matches an element in the array.
     * @return This instance.
     */
    @Nonnull
    public <T> MongoDBUpdate pull(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0,
            @Nonnull final MongoDBQuery mongoDBQuery) {
        assert f0 != null;
        assert mongoDBQuery != null;

        final String fieldName = mongoPath(f0);
        modifiers.add(new Modifier(OPERATOR_PULL, fieldName) {
            @Override
            @Nullable
            Object toDBValue() throws MapperException {
                return mongoDBQuery.toDBObject();
            }
        });
        return this;
    }

    @Nonnull
    public <T> MongoDBUpdate pull(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3,
            @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_PULL, mongoPath(f0, f1, f2, f3), getElementMapper(f3.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate popFirst(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0) {
        assert f0 != null;
        final Integer value = -1;
        return addModifier(OPERATOR_POP_FIRST, mongoPath(f0), IdentityMapper.getInstance(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate popFirst(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1) {
        assert f0 != null;
        assert f1 != null;
        final Integer value = -1;
        return addModifier(OPERATOR_POP_FIRST, mongoPath(f0, f1), IdentityMapper.getInstance(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate popFirst(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        final Integer value = -1;
        return addModifier(OPERATOR_POP_FIRST, mongoPath(f0, f1, f2), IdentityMapper.getInstance(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate popFirst(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        final Integer value = -1;
        return addModifier(OPERATOR_POP_FIRST, mongoPath(f0, f1, f2, f3), IdentityMapper.getInstance(), value);
    }

    @Nonnull
    public <T> MongoDBUpdate popLast(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0) {
        assert f0 != null;
        return addModifier(OPERATOR_POP_LAST, mongoPath(f0), IdentityMapper.getInstance(), 1);
    }

    @Nonnull
    public <T> MongoDBUpdate popLast(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1) {
        assert f0 != null;
        assert f1 != null;
        return addModifier(OPERATOR_POP_LAST, mongoPath(f0, f1), IdentityMapper.getInstance(), 1);
    }

    @Nonnull
    public <T> MongoDBUpdate popLast(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_POP_LAST, mongoPath(f0, f1, f2), IdentityMapper.getInstance(), 1);
    }

    @Nonnull
    public <T> MongoDBUpdate popLast(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_POP_LAST, mongoPath(f0, f1, f2, f3), IdentityMapper.getInstance(), 1);
    }

    @Nonnull
    public <T> MongoDBUpdate inc(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0, @Nullable final T value) {
        assert f0 != null;
        return addModifier(OPERATOR_INC, mongoPath(f0), getElementMapper(f0.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate inc(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        return addModifier(OPERATOR_INC, mongoPath(f0, f1), getElementMapper(f1.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate inc(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_INC, mongoPath(f0, f1, f2), getElementMapper(f2.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate inc(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3,
            @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_INC, mongoPath(f0, f1, f2, f3), getElementMapper(f3.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate addToSet(@Nonnull final EntityMapper<?>.Field<Collection<T>> f0,
            @Nullable final T value) {
        assert f0 != null;
        return addModifier(OPERATOR_ADD_TO_SET, mongoPath(f0), getElementMapper(f0.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate addToSet(@Nonnull final HasFieldName f0,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f1, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert value != null;
        return addModifier(OPERATOR_ADD_TO_SET, mongoPath(f0, f1), getElementMapper(f1.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate addToSet(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final EntityMapper<?>.Field<Collection<T>> f2, @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        return addModifier(OPERATOR_ADD_TO_SET, mongoPath(f0, f1, f2), getElementMapper(f2.getMapper()), value);
    }

    @Nonnull
    public <T> MongoDBUpdate addToSet(@Nonnull final HasFieldName f0, @Nonnull final HasFieldName f1,
            @Nonnull final HasFieldName f2, @Nonnull final EntityMapper<?>.Field<Collection<T>> f3,
            @Nullable final T value) {
        assert f0 != null;
        assert f1 != null;
        assert f2 != null;
        assert f3 != null;
        return addModifier(OPERATOR_ADD_TO_SET, mongoPath(f0, f1, f2, f3), getElementMapper(f3.getMapper()), value);
    }

    @Nonnull
    public DBObject toDBObject() throws MapperException {
        final DBObject result = new BasicDBObject();
        for (final Modifier modifier : modifiers) {
            BasicDBObject existing = (BasicDBObject) result.get(modifier.operator);
            if (existing == null) {
                existing = new BasicDBObject();
            }
            result.put(modifier.operator, existing.append(modifier.fieldName, modifier.toDBValue()));
        }
        return result;
    }

    @Nonnull
    private <T> MongoDBUpdate addModifier(@Nonnull final String operator, @Nonnull final String fieldName,
            @Nonnull final Mapper<Collection<T>> mapper, @Nonnull final Collection<T> values) {
        assert operator != null;
        assert fieldName != null;
        assert mapper != null;
        assert values != null;
        modifiers.add(new Modifier(operator, fieldName) {
            @Override
            @Nonnull
            Object toDBValue() throws MapperException {
                final List<Object> mappedValues = new ArrayList<>();
                final Mapper<T> elementMapper = getElementMapper(mapper);
                for (@Nullable
                final T value : values) {
                    mappedValues.add(elementMapper.toDb(value));
                }
                return mappedValues;
            }
        });
        return this;
    }

    @Nonnull
    private <T> MongoDBUpdate addModifier(@Nonnull final String operator, @Nonnull final String fieldName,
            @Nonnull final Mapper<T> mapper, @Nullable final T value) {
        assert operator != null;
        assert fieldName != null;
        assert mapper != null;
        modifiers.add(new Modifier(operator, fieldName) {
            @Override
            @Nullable
            Object toDBValue() throws MapperException {
                return mapper.toDb(value);
            }
        });
        return this;
    }

    @SuppressWarnings("unchecked")
    @Nonnull
    static <T> Mapper<T> getElementMapper(@Nonnull final Mapper<Collection<T>> mapper) {
        assert mapper instanceof CollectionMapper;
        return ((CollectionMapper) mapper).getElementMapper();
    }

    private static abstract class Modifier {
        @Nonnull
        public final String operator;
        @Nonnull
        public final String fieldName;

        private Modifier(@Nonnull final String operator, @Nonnull final String fieldName) {
            assert operator != null;
            assert fieldName != null;
            this.operator = operator;
            this.fieldName = fieldName;
        }

        @Nullable
        abstract Object toDBValue() throws MapperException;
    }

    @Override
    public String toString() {
        final StringBuilder out = new StringBuilder();
        out.append('{');
        String sep = "";
        for (final Modifier modifier : modifiers) {
            out.append(sep);
            sep = ", ";
            Object value;
            try {
                value = modifier.toDBValue();
            } catch (final MapperException ignored) {
                value = "?";
            }
            out.append(modifier.fieldName).append(':');
            if (modifier.operator.isEmpty()) {
                out.append(modifier.operator).append('(').append(value).append(')');
            } else {
                out.append(value);
            }
        }
        out.append('}');
        return out.toString();
    }
}