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    @Override
081    public boolean isMultiTenant() {
082        return schemaFieldMap.containsKey(TENANT_ID_FIELD);
083    }
084
085    protected void initializeIfNeeded(MongoDBSession session) {
086        if (!initialized) {
087            synchronized (this) {
088                if (!initialized) {
089                    initialize(session);
090                    initialized = true;
091                }
092            }
093        }
094    }
095
096    protected void initialize(MongoDBSession session) {
097        initSchemaFieldMap();
098
099        // Initialize counters collection if autoincrement enabled
100        if (descriptor.isAutoincrementIdField() && !session.hasCollection(countersCollectionName)) {
101            Map<String, Object> seq = new HashMap<>();
102            seq.put(MONGODB_ID, getName());
103            seq.put(MONGODB_SEQ, 0L);
104            session.getCollection(countersCollectionName).insertOne(MongoDBSerializationHelper.fieldMapToBson(seq));
105        }
106
107        String policy = descriptor.getCreateTablePolicy();
108        MongoCollection collection = session.getCollection(getName());
109        boolean dropCollection = false;
110        boolean loadData = false;
111
112        switch (policy) {
113        case CREATE_TABLE_POLICY_ALWAYS:
114            dropCollection = true;
115            loadData = true;
116            break;
117        case CREATE_TABLE_POLICY_ON_MISSING_COLUMNS:
118            // As MongoDB does not have the notion of columns, only load data if collection doesn't exist
119            if (!session.hasCollection(getName())) {
120                loadData = true;
121            }
122        default:
123            break;
124        }
125        if (dropCollection) {
126            collection.drop();
127        }
128        if (loadData) {
129            SchemaManager schemaManager = Framework.getService(SchemaManager.class);
130            Schema schema = schemaManager.getSchema(getSchema());
131            loadData(schema, session);
132        }
133    }
134
135    protected void loadData(Schema schema, Session session) {
136        if (descriptor.getDataFileName() != null) {
137            Framework.doPrivileged(() -> DirectoryCSVLoader.loadData(descriptor.getDataFileName(),
138                    descriptor.getDataFileCharacterSeparator(), schema, session::createEntry));
139        }
140    }
141
142    protected void addMongoDBReferences(MongoDBReferenceDescriptor[] mongodbReferences) {
143        if (mongodbReferences != null) {
144            Arrays.stream(mongodbReferences).map(MongoDBReference::new).forEach(this::addReference);
145        }
146    }
147
148    public String getCountersCollectionName() {
149        return countersCollectionName;
150    }
151
152    @Override
153    public void shutdown() {
154        super.shutdown();
155    }
156
157}