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.nuxeo.ecm.core.schema.SchemaManager;
033import org.nuxeo.ecm.core.schema.types.Schema;
034import org.nuxeo.ecm.directory.AbstractDirectory;
035import org.nuxeo.ecm.directory.Directory;
036import org.nuxeo.ecm.directory.DirectoryCSVLoader;
037import org.nuxeo.ecm.directory.DirectoryException;
038import org.nuxeo.ecm.directory.Session;
039import org.nuxeo.runtime.api.Framework;
040
041import com.mongodb.client.MongoCollection;
042
043/**
044 * MongoDB implementation of a {@link Directory}
045 *
046 * @since 9.1
047 */
048public class MongoDBDirectory extends AbstractDirectory {
049
050    protected String countersCollectionName;
051
052    // used in double-checked locking for lazy init
053    protected volatile boolean initialized;
054
055    public MongoDBDirectory(MongoDBDirectoryDescriptor descriptor) {
056        super(descriptor, MongoDBReference.class);
057
058        // Add specific references
059        addMongoDBReferences(descriptor.getMongoDBReferences());
060
061        // cache fallback
062        fallbackOnDefaultCache();
063
064        countersCollectionName = getName() + ".counters";
065    }
066
067    @Override
068    public MongoDBDirectoryDescriptor getDescriptor() {
069        return (MongoDBDirectoryDescriptor) descriptor;
070    }
071
072    @Override
073    public Session getSession() throws DirectoryException {
074        MongoDBSession session = new MongoDBSession(this);
075        addSession(session);
076        initializeIfNeeded(session);
077        return session;
078    }
079
080    protected void initializeIfNeeded(MongoDBSession session) {
081        if (!initialized) {
082            synchronized (this) {
083                if (!initialized) {
084                    initialize(session);
085                    initialized = true;
086                }
087            }
088        }
089    }
090
091    protected void initialize(MongoDBSession session) {
092        initSchemaFieldMap();
093
094        // Initialize counters collection if autoincrement enabled
095        if (descriptor.isAutoincrementIdField() && !session.hasCollection(countersCollectionName)) {
096            Map<String, Object> seq = new HashMap<>();
097            seq.put(MONGODB_ID, getName());
098            seq.put(MONGODB_SEQ, 0L);
099            session.getCollection(countersCollectionName).insertOne(MongoDBSerializationHelper.fieldMapToBson(seq));
100        }
101
102        String policy = descriptor.getCreateTablePolicy();
103        MongoCollection collection = session.getCollection(getName());
104        boolean dropCollection = false;
105        boolean loadData = false;
106
107        switch (policy) {
108        case CREATE_TABLE_POLICY_ALWAYS:
109            dropCollection = true;
110            loadData = true;
111            break;
112        case CREATE_TABLE_POLICY_ON_MISSING_COLUMNS:
113            // As MongoDB does not have the notion of columns, only load data if collection doesn't exist
114            if (!session.hasCollection(getName())) {
115                loadData = true;
116            }
117        default:
118            break;
119        }
120        if (dropCollection) {
121            collection.drop();
122        }
123        if (loadData) {
124            SchemaManager schemaManager = Framework.getService(SchemaManager.class);
125            Schema schema = schemaManager.getSchema(getSchema());
126            loadData(schema, session);
127        }
128    }
129
130    protected void loadData(Schema schema, Session session) {
131        if (descriptor.getDataFileName() != null) {
132            Framework.doPrivileged(() -> DirectoryCSVLoader.loadData(descriptor.getDataFileName(),
133                    descriptor.getDataFileCharacterSeparator(), schema, session::createEntry));
134        }
135    }
136
137    protected void addMongoDBReferences(MongoDBReferenceDescriptor[] mongodbReferences) {
138        if (mongodbReferences != null) {
139            Arrays.stream(mongodbReferences).map(MongoDBReference::new).forEach(this::addReference);
140        }
141    }
142
143    public String getCountersCollectionName() {
144        return countersCollectionName;
145    }
146
147    @Override
148    public void shutdown() {
149        super.shutdown();
150    }
151
152}