com.mastfrog.asyncpromises.mongo.CollectionPromises.java Source code

Java tutorial

Introduction

Here is the source code for com.mastfrog.asyncpromises.mongo.CollectionPromises.java

Source

/*
 * The MIT License
 *
 * Copyright 2015 Tim Boudreau.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mastfrog.asyncpromises.mongo;

import com.mastfrog.asyncpromises.AsyncPromise;
import com.mastfrog.asyncpromises.Logic;
import com.mastfrog.asyncpromises.PromiseContext;
import com.mastfrog.asyncpromises.SimpleLogic;
import com.mastfrog.asyncpromises.Trigger;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.async.AsyncBatchCursor;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.FindIterable;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.model.BulkWriteOptions;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.InsertManyOptions;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.model.WriteModel;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import java.util.ArrayList;
import java.util.List;
import org.bson.conversions.Bson;

/**
 * Promise wrapper for a MongoCollection.
 *
 * @author Tim Boudreau
 */
public class CollectionPromises<T> {

    private final MongoCollection<T> collection;

    /**
     * Create a wrapper.
     *
     * @param collection The collection
     */
    public CollectionPromises(MongoCollection<T> collection) {
        if (collection == null) {
            throw new IllegalArgumentException("Collection null");
        }
        this.collection = collection;
    }

    /**
     * Get the underlying collection.
     *
     * @return The collection
     */
    public MongoCollection<T> collection() {
        return collection;
    }

    /**
     * Create a copy over a the collection with an alternate element type.
     *
     * @param <R> The type
     * @param type The type
     * @return A promise wrapper
     */
    public <R> CollectionPromises<R> withType(Class<R> type) {
        return new CollectionPromises<R>(collection.withDocumentClass(type));
    }

    /**
     * Set the write concern and return a new object
     *
     * @param concern The write concern
     * @return A new CollectionPromises
     */
    public CollectionPromises<T> withWriteConcern(WriteConcern concern) {
        return new CollectionPromises<>(collection.withWriteConcern(concern));
    }

    /**
     * Set the read concern and return a new object
     *
     * @param pref The write preference
     * @return A new CollectionPromises
     */
    public CollectionPromises<T> withReadPreference(ReadPreference pref) {
        return new CollectionPromises<>(collection.withReadPreference(pref));
    }

    /**
     * Create a promise to delete one element when passed the query.
     *
     * @return a promise
     */
    public AsyncPromise<Bson, DeleteResult> deleteOne() {
        return AsyncPromise.create(new Logic<Bson, DeleteResult>() {
            @Override
            public void run(Bson data, Trigger<DeleteResult> next, PromiseContext context) throws Exception {
                collection.deleteOne(data, new SRC<>(next));
            }
        });
    }

    public AsyncPromise<Bson, UpdateResult> replaceOne(final T replacement, final UpdateOptions opts) {
        return AsyncPromise.create(new Logic<Bson, UpdateResult>() {

            @Override
            public void run(Bson data, Trigger<UpdateResult> next, PromiseContext context) throws Exception {
                collection.replaceOne(data, replacement, new SRC<>(next));
            }
        });
    }

    public QueryBuilder<T, ReplaceBuilder<AsyncPromise<Void, UpdateResult>, T>> replaceOne() {
        return QueryBuilderImpl.createForReplace(this);
    }

    /**
     * Create a promsie to delete many elements when passed the query.
     *
     * @return A promise
     */
    public AsyncPromise<Bson, DeleteResult> deleteMany() {
        return AsyncPromise.create(new Logic<Bson, DeleteResult>() {
            @Override
            public void run(Bson data, Trigger<DeleteResult> next, PromiseContext context) throws Exception {
                collection.deleteMany(data, new SRC<>(next));
            }
        });
    }

    /**
     * Create a promsie to insert one element when passed the element to insert.
     *
     * @return A promise
     */
    public AsyncPromise<T, Void> insertOne() {
        return AsyncPromise.create(new Logic<T, Void>() {
            @Override
            public void run(final T data, final Trigger<Void> next, PromiseContext context) throws Exception {
                collection.insertOne(data, new SRC<>(next));
            }
        });
    }

    /**
     * Create a promise to insert many elements when passed the elements to
     * insert.
     *
     * @return A promise
     */
    public AsyncPromise<List<T>, Void> insertMany() {
        return insertMany(new InsertManyOptions());
    }

    /**
     * Create a promise to insert many elements when passed the elements to
     * insert.
     *
     * @param opts The insert options
     * @return A promise
     */
    public AsyncPromise<List<T>, Void> insertMany(final InsertManyOptions opts) {
        return AsyncPromise.create(new Logic<List<T>, Void>() {
            @Override
            public void run(List<T> data, Trigger<Void> next, PromiseContext context) throws Exception {
                collection.insertMany(data, opts, new SRC<>(next));
            }
        });
    }

    /**
     * Update one element when the returned promise is passed the query.
     *
     * @param modification The modification to make (note that UpdateBuilder
     * offers a simpler way to correctly set MongoDB's update options.
     * @return A promise
     */
    public AsyncPromise<Bson, UpdateResult> updateOne(final Bson modification) {
        AsyncPromise<Bson, UpdateResult> m = AsyncPromise.create(new Logic<Bson, UpdateResult>() {
            @Override
            public void run(Bson data, Trigger<UpdateResult> next, PromiseContext context) throws Exception {
                collection.updateOne(data, modification, new SRC<>(next));
            }
        });
        return m;
    }

    /**
     * Update one element when the returned promise is passed the query.
     *
     * @param modification The modification to make (note that UpdateBuilder
     * offers a simpler way to correctly set MongoDB's update options.
     * @return A promise
     */
    public AsyncPromise<Bson, UpdateResult> updateMany(final Bson modification) {
        return updateMany(modification, new UpdateOptions());
    }

    /**
     * Update one element when the returned promise is passed the query.
     *
     * @param modification The modification to make (note that UpdateBuilder
     * offers a simpler way to correctly set MongoDB's update options.
     * @param opts The update options
     * @return A promise
     */
    public AsyncPromise<Bson, UpdateResult> updateMany(final Bson modification, final UpdateOptions opts) {
        AsyncPromise<Bson, UpdateResult> m = AsyncPromise.create(new Logic<Bson, UpdateResult>() {
            @Override
            public void run(Bson data, Trigger<UpdateResult> next, PromiseContext context) throws Exception {
                collection.updateMany(data, modification, opts, new SRC<>(next));
            }
        });
        return m;
    }

    /**
     * Create a promise for find one and update.
     *
     * @param modification The modification (the query will be passed to the
     * returned promise's start() method).
     * @param opts The options
     * @return A promise
     */
    public AsyncPromise<Bson, T> findOneAndUpdate(final Bson modification, final FindOneAndUpdateOptions opts) {
        AsyncPromise<Bson, T> m = AsyncPromise.create(new Logic<Bson, T>() {

            @Override
            public void run(Bson data, Trigger<T> next, PromiseContext context) throws Exception {
                collection.findOneAndUpdate(data, modification, opts, new SRC<T>(next));
            }
        });
        return m;
    }

    /**
     * Create a builder for find-and-update. The result of this is a chained set
     * of builders (you will call build() several times) - first you set up the
     * query, then the modification, then the find and update options.
     *
     * @return A query builder
     */
    public QueryBuilder<T, ModificationBuilder<FindOneAndUpdateBuilder<T, Void>>> findOneAndUpdate() {
        return QueryBuilderImpl.createForFindAndModify(this);
    }

    /**
     * Perform a find() or findOne() using a query builder to assemble the
     * query. first.
     *
     * @return
     */
    public QueryBuilder<T, FindBuilder<T, Void>> query() {
        return QueryBuilderImpl.create(this);
    }

    /**
     * Get a builder to configure and execute an update of one or many.
     * documents.
     *
     * @return The update builder
     */
    public UpdateBuilder<AsyncPromise<Bson, UpdateResult>> update() {
        return UpdateBuilderImpl.create(this);
    }

    /**
     * Create a promise for updating using a builder.
     *
     * @return A builder
     */
    public QueryBuilder<T, ModificationBuilder<UpdateBuilder<AsyncPromise<Void, UpdateResult>>>> updateWithQuery() {
        return QueryBuilderImpl.createForUpdate(this);
    }

    /**
     * Get a builder for bulk writes.
     *
     * @return A builder
     */
    public BulkWriteBuilder<T> bulkWrite() {
        return BulkWriteBuilderImpl.create(this);
    }

    /**
     * Perform a bulk write, passing the raw arguments.
     *
     * @param requests The requests
     * @param opts The options
     * @return A promise
     */
    public AsyncPromise<Void, BulkWriteResult> bulkWrite(List<WriteModel<? extends T>> requests,
            final BulkWriteOptions opts) {
        final List<WriteModel<? extends T>> reqs = new ArrayList<>(requests);
        return AsyncPromise.create(new Logic<Void, BulkWriteResult>() {

            @Override
            public void run(Void data, Trigger<BulkWriteResult> next, PromiseContext context) throws Exception {
                collection.bulkWrite(reqs, opts, new SRC<BulkWriteResult>(next));
            }
        });
    }

    AsyncPromise<Bson, T> findOne(final FindBuilderImpl<T, ?> builder) {
        AsyncPromise<Bson, T> m = AsyncPromise.create(new SimpleLogic<Bson, T>() {
            @Override
            public void run(Bson data, Trigger<T> next) throws Exception {
                FindIterable<T> find = builder.apply(collection.find(data));
                find.first(new SRC<>(next));
            }
        });
        return m;
    }

    /**
     * Count documents, supplying your own Bson to the promise.
     *
     * @return A CountBuilder
     */
    public CountBuilder<Bson> count() {
        return CountBuilderImpl.create(this);
    }

    /**
     * Count documents, using a QueryBuilder to build the query to match
     * against.
     *
     * @return A query builder
     */
    public QueryBuilder<T, CountBuilder<Void>> countWithQuery() {
        QueryBuilderImpl<T, CountBuilder<Void>> x = QueryBuilderImpl.createForCount(this);
        return x;
    }

    /**
     * Count documents, supplying your own Bson to the promise and using the
     * passed count options.
     *
     * @param opts The count options
     * @return A promise
     */
    public AsyncPromise<Bson, Long> count(final CountOptions opts) {
        return AsyncPromise.create(new SimpleLogic<Bson, Long>() {

            @Override
            public void run(Bson data, Trigger<Long> next) throws Exception {
                collection.count(data, opts, new SRC<>(next));
            }
        });
    }

    AsyncPromise<Bson, List<T>> find(final FindBuilderImpl<T, ?> builder, final FindReceiver<List<T>> withResults) {
        AsyncPromise<Bson, AsyncBatchCursor<T>> m = AsyncPromise.create(new Logic<Bson, AsyncBatchCursor<T>>() {
            @Override
            public void run(Bson data, Trigger<AsyncBatchCursor<T>> next, PromiseContext context) throws Exception {
                builder.apply(collection.find(data)).batchCursor(new SRC<>(next));
            }
        });
        final ContinueTrigger cont = new ContinueTrigger();
        AsyncPromise<AsyncBatchCursor<T>, List<T>> cursorPromise = AsyncPromise
                .create(new Logic<AsyncBatchCursor<T>, List<T>>() {
                    @Override
                    public void run(final AsyncBatchCursor<T> cursor, final Trigger<List<T>> next,
                            final PromiseContext context) throws Exception {
                        cursor.next(new SRC<>(new Trigger<List<T>>() {
                            @Override
                            public void trigger(List<T> obj, Throwable thrown) {
                                if (thrown != null) {
                                    cursor.close();
                                    next.trigger(obj, thrown);
                                    return;
                                }
                                if (obj != null) {
                                    try {
                                        withResults.withResults(obj, cont, context);
                                    } catch (Exception ex) {
                                        next.trigger(obj, ex);
                                        return;
                                    }
                                }
                                if (cont.get()) {
                                    cursor.next(new SRC<List<T>>(this));
                                } else {
                                    cursor.close();
                                    next.trigger(obj, thrown);
                                }
                            }
                        }));
                    }
                });
        return m.then(cursorPromise);
    }

    FindBuilderImpl<T, Bson> findImpl() {
        return FindBuilderImpl.create(this);
    }

    /**
     * Perform a find operation - use the builder to set up the parameters of
     * the query, then use one of its find*() methods to get a promise you can
     * pass the query itself to.
     *
     * @return A find builder
     */
    public FindBuilder<T, Bson> find() {
        return findImpl();
    }

    private static class SRC<T> implements SingleResultCallback<T> {

        private final Trigger<T> trigger;

        public SRC(Trigger<T> trigger) {
            this.trigger = trigger;
        }

        @Override
        public void onResult(T t, Throwable thrwbl) {
            trigger.trigger(t, thrwbl);
        }
    }
}