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