/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.mongodb.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.integration.mongodb.support.BinaryToMessageConverter;
import org.springframework.integration.mongodb.support.MessageToBinaryConverter;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.BasicMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageMetadata;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* The abstract MongoDB {@link BasicMessageGroupStore} implementation to provide configuration for common options
* for implementations of this class.
*
* @author Artem Bilan
* @since 4.0
*/
public abstract class AbstractConfigurableMongoDbMessageStore extends AbstractMessageGroupStore
implements BasicMessageGroupStore, InitializingBean, ApplicationContextAware {
public final static String SEQUENCE_NAME = "messagesSequence";
/**
* The name of the message header that stores a flag to indicate that the message has been saved. This is an
* optimization for the put method.
* @deprecated since 5.0. This constant isn't used any more.
*/
@Deprecated
public static final String SAVED_KEY = "MongoDbMessageStore.SAVED";
/**
* The name of the message header that stores a timestamp for the time the message was inserted.
* @deprecated since 5.0. This constant isn't used any more.
*/
@Deprecated
public static final String CREATED_DATE_KEY = "MongoDbMessageStore.CREATED_DATE";
protected final Log logger = LogFactory.getLog(this.getClass());
protected final String collectionName;
protected final MongoDbFactory mongoDbFactory;
protected MongoTemplate mongoTemplate;
protected MappingMongoConverter mappingMongoConverter;
protected ApplicationContext applicationContext;
protected MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
public AbstractConfigurableMongoDbMessageStore(MongoTemplate mongoTemplate, String collectionName) {
Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null");
Assert.hasText(collectionName, "'collectionName' must not be empty");
this.collectionName = collectionName;
this.mongoTemplate = mongoTemplate;
this.mongoDbFactory = null;
}
public AbstractConfigurableMongoDbMessageStore(MongoDbFactory mongoDbFactory, String collectionName) {
this(mongoDbFactory, null, collectionName);
}
public AbstractConfigurableMongoDbMessageStore(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter, String collectionName) {
Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null");
Assert.hasText(collectionName, "'collectionName' must not be empty");
this.collectionName = collectionName;
this.mongoDbFactory = mongoDbFactory;
this.mappingMongoConverter = mappingMongoConverter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.mongoTemplate == null) {
if (this.mappingMongoConverter == null) {
this.mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(this.mongoDbFactory),
new MongoMappingContext());
this.mappingMongoConverter.setApplicationContext(this.applicationContext);
List<Object> customConverters = new ArrayList<Object>();
customConverters.add(new MessageToBinaryConverter());
customConverters.add(new BinaryToMessageConverter());
this.mappingMongoConverter.setCustomConversions(new MongoCustomConversions(customConverters));
this.mappingMongoConverter.afterPropertiesSet();
}
this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mappingMongoConverter);
}
this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.applicationContext);
IndexOperations indexOperations = this.mongoTemplate.indexOps(this.collectionName);
indexOperations.ensureIndex(new Index(MessageDocumentFields.MESSAGE_ID, Sort.Direction.ASC));
indexOperations.ensureIndex(new Index(MessageDocumentFields.GROUP_ID, Sort.Direction.ASC)
.on(MessageDocumentFields.MESSAGE_ID, Sort.Direction.ASC)
.unique());
indexOperations.ensureIndex(new Index(MessageDocumentFields.GROUP_ID, Sort.Direction.ASC)
.on(MessageDocumentFields.LAST_MODIFIED_TIME, Sort.Direction.DESC)
.on(MessageDocumentFields.SEQUENCE, Sort.Direction.DESC));
}
public Message<?> getMessage(UUID id) {
Assert.notNull(id, "'id' must not be null");
Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).is(id));
MessageDocument document = this.mongoTemplate.findOne(query, MessageDocument.class, this.collectionName);
return document != null ? document.getMessage() : null;
}
public MessageMetadata getMessageMetadata(UUID id) {
Assert.notNull(id, "'id' must not be null");
Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).is(id));
MessageDocument document = this.mongoTemplate.findOne(query, MessageDocument.class, this.collectionName);
if (document != null) {
MessageMetadata messageMetadata = new MessageMetadata(id);
messageMetadata.setTimestamp(document.getCreatedTime());
return messageMetadata;
}
else {
return null;
}
}
@Override
public void removeMessageGroup(Object groupId) {
this.mongoTemplate.remove(groupIdQuery(groupId), this.collectionName);
}
@Override
public int messageGroupSize(Object groupId) {
long lCount = this.mongoTemplate.count(groupIdQuery(groupId), this.collectionName);
Assert.isTrue(lCount <= Integer.MAX_VALUE, "Message count is out of Integer's range");
return (int) lCount;
}
/**
* Perform MongoDB {@code INC} operation for the document, which contains the {@link MessageDocument}
* {@code sequence}, and return the new incremented value for the new {@link MessageDocument}.
* The {@link #SEQUENCE_NAME} document is created on demand.
* @return the next sequence value.
*/
protected int getNextId() {
Query query = Query.query(Criteria.where("_id").is(SEQUENCE_NAME));
query.fields().include(MessageDocumentFields.SEQUENCE);
return (Integer) this.mongoTemplate.findAndModify(query,
new Update().inc(MessageDocumentFields.SEQUENCE, 1),
FindAndModifyOptions.options().returnNew(true).upsert(true),
Map.class, this.collectionName)
.get(MessageDocumentFields.SEQUENCE);
}
protected void addMessageDocument(final MessageDocument document) {
if (document.getGroupCreatedTime() == 0) {
document.setGroupCreatedTime(System.currentTimeMillis());
}
document.setCreatedTime(System.currentTimeMillis());
try {
this.mongoTemplate.insert(document, this.collectionName);
}
catch (DuplicateKeyException e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("The Message with id [" + document.getMessageId() + "] already exists.\n" +
"Ignoring INSERT and SELECT existing...");
}
}
}
protected static Query groupIdQuery(Object groupId) {
return Query.query(Criteria.where(MessageDocumentFields.GROUP_ID).is(groupId));
}
@Override
public void removeMessagesFromGroup(Object key, Collection<Message<?>> messages) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public Iterator<MessageGroup> iterator() {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public void completeGroup(Object groupId) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public Message<?> getOneMessageFromGroup(Object groupId) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public void addMessagesToGroup(Object groupId, Message<?>... messages) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
@Override
public Collection<Message<?>> getMessagesForGroup(Object groupId) {
throw new UnsupportedOperationException("The operation isn't implemented for this class.");
}
}