org.restheart.db.DatabaseImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.restheart.db.DatabaseImpl.java

Source

/*
 * RESTHeart - the Web API for MongoDB
 * Copyright (C) SoftInstigate Srl
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.restheart.db;

import com.mongodb.MongoClient;
import com.mongodb.client.ClientSession;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import static com.mongodb.client.model.Filters.eq;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.bson.BsonDocument;
import org.bson.BsonObjectId;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.restheart.handlers.IllegalQueryParamenterException;
import org.restheart.handlers.RequestContext;
import static org.restheart.handlers.RequestContext.META_COLLNAME;
import static org.restheart.handlers.RequestContext.DB_META_DOCID;
import org.restheart.handlers.injectors.LocalCachesSingleton;
import org.restheart.utils.HttpStatus;

/**
 *
 * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
 */
public class DatabaseImpl implements Database {

    public static final Bson PROPS_QUERY = eq("_id", DB_META_DOCID);

    private static final Document FIELDS_TO_RETURN;

    static {
        FIELDS_TO_RETURN = new Document();
        FIELDS_TO_RETURN.put("_id", 1);
        FIELDS_TO_RETURN.put("_etag", 1);
    }

    /**
     * delegated object for collection operations
     */
    private final CollectionDAO collectionDAO;
    private final IndexDAO indexDAO;

    private final MongoClient client;

    public DatabaseImpl() {
        client = MongoDBClientSingleton.getInstance().getClient();
        this.collectionDAO = new CollectionDAO(client);
        this.indexDAO = new IndexDAO(client);
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @return
     *
     */
    @Override
    public boolean doesDbExist(final ClientSession cs, final String dbName) {
        // at least one collection exists for an existing db
        return cs == null ? client.getDatabase(dbName).listCollectionNames().first() != null
                : client.getDatabase(dbName).listCollectionNames(cs).first() != null;
    }

    /**
     * Returns true if the collection exists
     *
     * @param cs the client session
     * @param dbName the database name of the collection
     * @param collName the collection name
     * @return true if the collection exists
     */
    @Override
    public boolean doesCollectionExist(final ClientSession cs, final String dbName, final String collName) {
        return collectionDAO.doesCollectionExist(cs, dbName, collName);
    }

    /**
     *
     * @param dbName
     * @return the MongoDatabase
     */
    @Override
    public MongoDatabase getDatabase(final String dbName) {
        return client.getDatabase(dbName);
    }

    /**
     *
     * @param cs the client session
     * @param dbName the database name of the collection
     * @return A ordered List of collection names
     */
    @Override
    public List<String> getCollectionNames(final ClientSession cs, final String dbName) {
        MongoDatabase db = getDatabase(dbName);

        List<String> _colls = new ArrayList<>();

        if (cs == null) {
            db.listCollectionNames().into(_colls);
        } else {
            db.listCollectionNames(cs).into(_colls);
        }

        // filter out reserved dbs
        return _colls.stream().filter(coll -> !RequestContext.isReservedResourceCollection(coll)).sorted()
                .collect(Collectors.toList());
    }

    /**
     * @param colls the collections list got from getCollectionNames()
     * @return the number of collections in this db
     *
     */
    @Override
    public long getDBSize(final List<String> colls) {
        // filter out reserved resources
        List<String> _colls = colls.stream().filter(coll -> !RequestContext.isReservedResourceCollection(coll))
                .collect(Collectors.toList());

        return _colls.size();
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @return the db props
     *
     */
    @Override
    public BsonDocument getDatabaseProperties(final ClientSession cs, final String dbName) {
        MongoCollection<BsonDocument> propsColl = collectionDAO.getCollection(dbName, META_COLLNAME);

        BsonDocument props = cs == null ? propsColl.find(PROPS_QUERY).limit(1).first()
                : propsColl.find(cs, PROPS_QUERY).limit(1).first();

        if (props != null) {
            props.append("_id", new BsonString(dbName));
        } else if (doesDbExist(cs, dbName)) {
            return new BsonDocument("_id", new BsonString(dbName));
        }

        return props;
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param colls the collections list as got from getCollectionNames()
     * @param page
     * @param pagesize
     * @return the db data
     * @throws org.restheart.handlers.IllegalQueryParamenterException
     *
     */
    @Override
    public List<BsonDocument> getDatabaseData(final ClientSession cs, final String dbName, final List<String> colls,
            final int page, final int pagesize) throws IllegalQueryParamenterException {
        // filter out reserved resources
        List<String> _colls = colls.stream().filter(coll -> !RequestContext.isReservedResourceCollection(coll))
                .collect(Collectors.toList());

        int size = _colls.size();

        // *** arguments check
        long total_pages;

        if (size > 0) {
            float _size = size + 0f;
            float _pagesize = pagesize + 0f;

            total_pages = Math.max(1, Math.round(Math.ceil(_size / _pagesize)));

            if (page > total_pages) {
                throw new IllegalQueryParamenterException(
                        "illegal query paramenter," + " page is bigger that total pages which is " + total_pages);
            }
        }

        // apply page and pagesize
        _colls = _colls.subList((page - 1) * pagesize,
                (page - 1) * pagesize + pagesize > _colls.size() ? _colls.size()
                        : (page - 1) * pagesize + pagesize);

        List<BsonDocument> data = new ArrayList<>();

        _colls.stream().map((collName) -> {
            BsonDocument properties = new BsonDocument("_id", new BsonString(collName));

            BsonDocument collProperties;

            if (LocalCachesSingleton.isEnabled()) {
                collProperties = LocalCachesSingleton.getInstance().getCollectionProperties(dbName, collName);
            } else {
                collProperties = collectionDAO.getCollectionProps(cs, dbName, collName);
            }

            if (collProperties != null) {
                properties.putAll(collProperties);
            }

            return properties;
        }).forEach((item) -> {
            data.add(item);
        });

        return data;
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param newContent
     * @param requestEtag
     * @param patching
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public OperationResult upsertDB(final ClientSession cs, final String dbName, final BsonDocument newContent,
            final String requestEtag, final boolean updating, final boolean patching, final boolean checkEtag) {

        if (patching && !updating) {
            return new OperationResult(HttpStatus.SC_NOT_FOUND);
        }

        ObjectId newEtag = new ObjectId();

        final BsonDocument content = DAOUtils.validContent(newContent);

        content.put("_etag", new BsonObjectId(newEtag));
        content.remove("_id"); // make sure we don't change this field

        MongoDatabase mdb = client.getDatabase(dbName);
        MongoCollection<BsonDocument> mcoll = mdb.getCollection(META_COLLNAME, BsonDocument.class);

        if (checkEtag && updating) {
            BsonDocument oldProperties = cs == null
                    ? mcoll.find(eq("_id", DB_META_DOCID)).projection(FIELDS_TO_RETURN).first()
                    : mcoll.find(cs, eq("_id", DB_META_DOCID)).projection(FIELDS_TO_RETURN).first();

            if (oldProperties != null) {
                BsonValue oldEtag = oldProperties.get("_etag");

                if (oldEtag != null && requestEtag == null) {
                    return new OperationResult(HttpStatus.SC_CONFLICT, oldEtag);
                }

                BsonValue _requestEtag;

                if (ObjectId.isValid(requestEtag)) {
                    _requestEtag = new BsonObjectId(new ObjectId(requestEtag));
                } else {
                    // restheart generates ObjectId etags, but here we support
                    // strings as well
                    _requestEtag = new BsonString(requestEtag);
                }

                if (Objects.equals(_requestEtag, oldEtag)) {
                    return doDbPropsUpdate(cs, patching, updating, mcoll, content, newEtag);
                } else {
                    return new OperationResult(HttpStatus.SC_PRECONDITION_FAILED, oldEtag);
                }
            } else {
                // this is the case when the db does not have properties
                // e.g. it has not been created by restheart
                return doDbPropsUpdate(cs, patching, updating, mcoll, content, newEtag);
            }
        } else {
            return doDbPropsUpdate(cs, patching, updating, mcoll, content, newEtag);
        }
    }

    private OperationResult doDbPropsUpdate(final ClientSession cs, final boolean patching, final boolean updating,
            final MongoCollection<BsonDocument> mcoll, final BsonDocument dcontent, final ObjectId newEtag) {
        if (patching) {
            OperationResult ret = DAOUtils.updateDocument(cs, mcoll, DB_META_DOCID, null, null, dcontent, false);
            return new OperationResult(ret.getHttpCode() > 0 ? ret.getHttpCode() : HttpStatus.SC_OK, newEtag);
        } else if (updating) {
            OperationResult ret = DAOUtils.updateDocument(cs, mcoll, DB_META_DOCID, null, null, dcontent, true);
            return new OperationResult(ret.getHttpCode() > 0 ? ret.getHttpCode() : HttpStatus.SC_OK, newEtag);
        } else {
            OperationResult ret = DAOUtils.updateDocument(cs, mcoll, DB_META_DOCID, null, null, dcontent, false);
            return new OperationResult(ret.getHttpCode() > 0 ? ret.getHttpCode() : HttpStatus.SC_CREATED, newEtag);
        }
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param requestEtag
     * @return
     */
    @Override
    public OperationResult deleteDatabase(final ClientSession cs, final String dbName, final String requestEtag,
            final boolean checkEtag) {
        MongoDatabase mdb = client.getDatabase(dbName);
        MongoCollection<Document> mcoll = mdb.getCollection(META_COLLNAME);

        if (checkEtag) {
            var query = eq("_id", DB_META_DOCID);
            Document properties = cs == null ? mcoll.find(query).projection(FIELDS_TO_RETURN).first()
                    : mcoll.find(cs, query).projection(FIELDS_TO_RETURN).first();

            if (properties != null) {
                Object oldEtag = properties.get("_etag");

                if (oldEtag != null) {
                    if (requestEtag == null) {
                        return new OperationResult(HttpStatus.SC_CONFLICT, oldEtag);
                    } else if (!Objects.equals(oldEtag.toString(), requestEtag)) {
                        return new OperationResult(HttpStatus.SC_PRECONDITION_FAILED, oldEtag);
                    }
                }
            }
        }

        if (cs == null) {
            mdb.drop();
        } else {
            mdb.drop(cs);
        }

        return new OperationResult(HttpStatus.SC_NO_CONTENT);
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collName
     * @return
     */
    @Override
    public BsonDocument getCollectionProperties(final ClientSession cs, final String dbName,
            final String collName) {
        return collectionDAO.getCollectionProps(cs, dbName, collName);
    }

    /**
     *
     * @param dbName
     * @param collName
     * @return
     */
    @Override
    public MongoCollection<BsonDocument> getCollection(final String dbName, final String collName) {
        MongoDatabase mdb = client.getDatabase(dbName);
        MongoCollection<BsonDocument> mcoll = mdb.getCollection(collName, BsonDocument.class);
        return mcoll;
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collName
     * @param content
     * @param requestEtag
     * @param updating
     * @param patching
     * @param checkEtag
     * @return
     */
    @Override
    public OperationResult upsertCollection(final ClientSession cs, final String dbName, final String collName,
            final BsonDocument content, final String requestEtag, final boolean updating, final boolean patching,
            final boolean checkEtag) {
        return collectionDAO.upsertCollection(cs, dbName, collName, content, requestEtag, updating, patching,
                checkEtag);
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collectionName
     * @param requestEtag
     * @param checkEtag
     * @return
     */
    @Override
    public OperationResult deleteCollection(final ClientSession cs, final String dbName,
            final String collectionName, final String requestEtag, final boolean checkEtag) {
        return collectionDAO.deleteCollection(cs, dbName, collectionName, requestEtag, checkEtag);
    }

    /**
     *
     * @param cs the client session
     * @param coll
     * @param filters
     * @return
     */
    @Override
    public long getCollectionSize(final ClientSession cs, final MongoCollection<BsonDocument> coll,
            final BsonDocument filters) {
        return collectionDAO.getCollectionSize(cs, coll, filters);
    }

    /**
     *
     * @param cs the client session
     * @param coll
     * @param page
     * @param pagesize
     * @param sortBy
     * @param filter
     * @param hint
     * @param keys
     * @param cursorAllocationPolicy
     * @return
     */
    @Override
    public ArrayList<BsonDocument> getCollectionData(final ClientSession cs,
            final MongoCollection<BsonDocument> coll, final int page, final int pagesize, final BsonDocument sortBy,
            final BsonDocument filter, final BsonDocument hint, final BsonDocument keys,
            final CursorPool.EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy) {
        return collectionDAO.getCollectionData(cs, coll, page, pagesize, sortBy, filter, hint, keys,
                cursorAllocationPolicy);
    }

    /**
     *
     * @param cs the client session
     * @return
     */
    @Override
    public List<String> getDatabaseNames(final ClientSession cs) {
        ArrayList<String> dbNames = new ArrayList<>();

        if (cs == null) {
            client.listDatabaseNames().into(dbNames);
        } else {
            client.listDatabaseNames(cs).into(dbNames);
        }

        return dbNames;
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collection
     * @param indexId
     * @return
     */
    @Override
    public int deleteIndex(final ClientSession cs, final String dbName, final String collection,
            final String indexId) {
        return indexDAO.deleteIndex(cs, dbName, collection, indexId);
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collectionName
     * @return
     */
    @Override
    public List<BsonDocument> getCollectionIndexes(final ClientSession cs, final String dbName,
            final String collectionName) {
        return indexDAO.getCollectionIndexes(cs, dbName, collectionName);
    }

    /**
     *
     * @param cs the client session
     * @param collection
     * @param sortBy
     * @param filters
     * @param hint
     * @param keys
     * @return
     */
    @Override
    public FindIterable<BsonDocument> getFindIterable(final ClientSession cs,
            final MongoCollection<BsonDocument> collection, final BsonDocument sortBy, final BsonDocument filters,
            final BsonDocument hint, final BsonDocument keys) {
        return collectionDAO.getFindIterable(cs, collection, sortBy, filters, hint, keys);
    }

    /**
     *
     * @param cs the client session
     * @param dbName
     * @param collection
     * @param keys
     * @param options
     */
    @Override
    public void createIndex(final ClientSession cs, final String dbName, final String collection,
            final BsonDocument keys, final BsonDocument options) {
        indexDAO.createIndex(cs, dbName, collection, keys, options);
    }
}