001/*
002 * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Funsho David
018 *
019 */
020
021package org.nuxeo.directory.mongodb;
022
023import static org.nuxeo.directory.mongodb.MongoDBSerializationHelper.MONGODB_ID;
024import static org.nuxeo.directory.mongodb.MongoDBSerializationHelper.MONGODB_SEQ;
025import static org.nuxeo.ecm.directory.BaseDirectoryDescriptor.CREATE_TABLE_POLICY_ALWAYS;
026import static org.nuxeo.ecm.directory.BaseDirectoryDescriptor.CREATE_TABLE_POLICY_ON_MISSING_COLUMNS;
027
028import java.util.Arrays;
029import java.util.HashMap;
030import java.util.Map;
031
032import org.bson.Document;
033import org.nuxeo.ecm.directory.AbstractDirectory;
034import org.nuxeo.ecm.directory.Directory;
035import org.nuxeo.ecm.directory.Reference;
036import org.nuxeo.runtime.api.Framework;
037import org.nuxeo.runtime.mongodb.MongoDBConnectionHelper;
038import org.nuxeo.runtime.mongodb.MongoDBConnectionService;
039
040import com.mongodb.client.MongoCollection;
041import com.mongodb.client.MongoDatabase;
042import com.mongodb.client.model.Indexes;
043
044/**
045 * MongoDB implementation of a {@link Directory}
046 *
047 * @since 9.1
048 */
049public class MongoDBDirectory extends AbstractDirectory {
050
051    /**
052     * Prefix used to retrieve a MongoDB connection from {@link MongoDBConnectionService}.
053     * <p>
054     * The connection id will be {@code directory/[DIRECTORY_NAME]}.
055     *
056     * @since 10.10
057     */
058    public static final String DIRECTORY_CONNECTION_PREFIX = "directory/";
059
060    protected MongoDatabase database;
061
062    protected MongoCollection<Document> collection;
063
064    protected MongoCollection<Document> countersCollection;
065
066    public MongoDBDirectory(MongoDBDirectoryDescriptor descriptor) {
067        super(descriptor, MongoDBReference.class);
068
069        // cache fallback
070        fallbackOnDefaultCache();
071    }
072
073    @Override
074    public MongoDBDirectoryDescriptor getDescriptor() {
075        return (MongoDBDirectoryDescriptor) descriptor;
076    }
077
078    @Override
079    protected void addReferences() {
080        super.addReferences();
081        // add backward compat MongoDB references
082        MongoDBReferenceDescriptor[] descs = getDescriptor().getMongoDBReferences();
083        if (descs != null) {
084            Arrays.stream(descs).map(MongoDBReference::new).forEach(this::addReference);
085        }
086    }
087
088    @Override
089    public MongoDBSession getSession() {
090        MongoDBSession session = new MongoDBSession(this);
091        addSession(session);
092        return session;
093    }
094
095    @Override
096    public boolean isMultiTenant() {
097        return schemaFieldMap.containsKey(TENANT_ID_FIELD);
098    }
099
100    @Override
101    public void initialize() {
102        super.initialize();
103
104        MongoDBConnectionService mongoService = Framework.getService(MongoDBConnectionService.class);
105        database = mongoService.getDatabase(DIRECTORY_CONNECTION_PREFIX + getName());
106        collection = database.getCollection(getName());
107        String countersCollectionName = getName() + ".counters";
108        countersCollection = database.getCollection(countersCollectionName);
109
110        // Initialize counters collection if autoincrement enabled
111        if (descriptor.isAutoincrementIdField() && !hasCollection(countersCollectionName)) {
112            Map<String, Object> seq = new HashMap<>();
113            seq.put(MONGODB_ID, getName());
114            seq.put(MONGODB_SEQ, 0L);
115            getCountersCollection().insertOne(MongoDBSerializationHelper.fieldMapToBson(seq));
116        }
117
118        String policy = descriptor.getCreateTablePolicy();
119        boolean dropCollection = false;
120        boolean collectionExists = true;
121
122        switch (policy) {
123        case CREATE_TABLE_POLICY_ALWAYS:
124            dropCollection = true;
125            collectionExists = false;
126            break;
127        case CREATE_TABLE_POLICY_ON_MISSING_COLUMNS:
128            // As MongoDB does not have the notion of columns, only load data if collection doesn't exist
129            if (!hasCollection(getName())) {
130                collectionExists = false;
131            }
132            break;
133        default:
134            break;
135        }
136        if (dropCollection) {
137            collection.drop();
138        }
139        if (isMultiTenant()) {
140            collection.createIndex(Indexes.hashed(TENANT_ID_FIELD));
141        }
142
143        loadDataOnInit(collectionExists);
144
145    }
146
147    @Override
148    public void initializeReferences() {
149        try (MongoDBSession session = getSession()) {
150            for (Reference reference : getReferences()) {
151                if (reference instanceof MongoDBReference) {
152                    ((MongoDBReference) reference).initialize(session);
153                }
154            }
155        }
156    }
157
158    /**
159     * Checks if the MongoDB server has the collection.
160     *
161     * @param collection the collection name
162     * @return true if the server has the collection, false otherwise
163     */
164    protected boolean hasCollection(String collection) {
165        return MongoDBConnectionHelper.hasCollection(database, collection);
166    }
167
168    /**
169     * Retrieves the collection associated to this directory.
170     *
171     * @return the MongoDB collection
172     */
173    protected MongoCollection<Document> getCollection() {
174        return collection;
175    }
176
177    /**
178     * Retrieves the counters collection associated to this directory.
179     *
180     * @return the MongoDB counters collection
181     */
182    protected MongoCollection<Document> getCountersCollection() {
183        return countersCollection;
184    }
185
186}