001/*
002 * (C) Copyright 2017 Nuxeo SA (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 *     Kevin Leturc
018 */
019package org.nuxeo.mongodb.seqgen;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.bson.Document;
024import org.bson.conversions.Bson;
025import org.nuxeo.ecm.core.uidgen.AbstractUIDSequencer;
026import org.nuxeo.ecm.core.uidgen.UIDSequencer;
027import org.nuxeo.mongodb.core.MongoDBConnectionService;
028import org.nuxeo.mongodb.core.MongoDBSerializationHelper;
029import org.nuxeo.runtime.api.Framework;
030import org.nuxeo.runtime.services.config.ConfigurationService;
031
032import com.mongodb.MongoWriteException;
033import com.mongodb.client.MongoCollection;
034import com.mongodb.client.MongoDatabase;
035import com.mongodb.client.model.Filters;
036import com.mongodb.client.model.FindOneAndUpdateOptions;
037import com.mongodb.client.model.ReturnDocument;
038import com.mongodb.client.model.Updates;
039
040/**
041 * MongoDB implementation of {@link UIDSequencer}.
042 * <p>
043 * We use MongoDB upsert feature to provide a sequencer.
044 *
045 * @since 9.1
046 */
047public class MongoDBUIDSequencer extends AbstractUIDSequencer {
048
049    public static final String SEQUENCE_DATABASE_ID = "sequence";
050
051    public static final String COLLECTION_NAME_PROPERTY = "nuxeo.mongodb.seqgen.collection.name";
052
053    public static final String DEFAULT_COLLECTION_NAME = "sequence";
054
055    public static final Long ONE = Long.valueOf(1L);
056
057    public static final String SEQUENCE_VALUE_FIELD = "sequence";
058
059    private static final Log log = LogFactory.getLog(MongoDBUIDSequencer.class);
060
061    private MongoCollection<Document> coll;
062
063    @Override
064    public void init() {
065        getCollection();
066
067    }
068
069    private MongoCollection<Document> getCollection() {
070        if (coll == null) {
071            // Get collection name
072            ConfigurationService configurationService = Framework.getService(ConfigurationService.class);
073            String collName = configurationService.getProperty(COLLECTION_NAME_PROPERTY, DEFAULT_COLLECTION_NAME);
074            // Get a connection to MongoDB
075            MongoDBConnectionService mongoService = Framework.getService(MongoDBConnectionService.class);
076            // Get database
077            MongoDatabase database = mongoService.getDatabase(SEQUENCE_DATABASE_ID);
078            // Get collection
079            coll = database.getCollection(collName);
080        }
081        return coll;
082    }
083
084    @Override
085    public int getNext(String key) {
086        return (int) getNextLong(key);
087    }
088
089    @Override
090    public long getNextLong(String key) {
091        FindOneAndUpdateOptions options = new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER);
092        Bson filter = Filters.eq(MongoDBSerializationHelper.MONGODB_ID, key);
093        Bson update = Updates.inc(SEQUENCE_VALUE_FIELD, ONE);
094        Document sequence = getCollection().findOneAndUpdate(filter, update, options);
095        // If sequence is null, we need to create it
096        if (sequence == null) {
097            try {
098                sequence = new Document();
099                sequence.put(MongoDBSerializationHelper.MONGODB_ID, key);
100                sequence.put(SEQUENCE_VALUE_FIELD, ONE);
101                getCollection().insertOne(sequence);
102            } catch (MongoWriteException e) {
103                // There was a race condition - just re-run getNextLong
104                if (log.isTraceEnabled()) {
105                    log.trace("There was a race condition during '" + key + "' sequence insertion", e);
106                }
107                return getNextLong(key);
108            }
109        }
110        return ((Long) MongoDBSerializationHelper.bsonToFieldMap(sequence).get(SEQUENCE_VALUE_FIELD)).longValue();
111    }
112
113    @Override
114    public void dispose() {
115        if (coll != null) {
116            coll = null;
117        }
118    }
119
120}