/* * Copyright 2007-2016 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.inbound; import java.util.List; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.expression.Expression; import org.springframework.expression.TypeLocator; import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.core.MessageSource; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.mongodb.support.MongoHeaders; import org.springframework.integration.transaction.IntegrationResourceHolder; import org.springframework.messaging.Message; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import com.mongodb.DBObject; /** * An instance of {@link MessageSource} which returns a {@link Message} with a payload * which is the result of execution of a {@link Query}. When expectSingleResult is false * (default), * the MongoDb {@link Query} is executed using {@link MongoOperations#find(Query, Class)} * method which returns a {@link List}. The returned {@link List} will be used as * the payoad of the {@link Message} returned by the {{@link #receive()} method. * An empty {@link List} is treated as null, thus resulting in no {@link Message} returned * by the {{@link #receive()} method. * <p> * When expectSingleResult is true, the {@link MongoOperations#findOne(Query, Class)} is * used instead, and the message payload will be the single object returned from the * query. * * @author Amol Nayak * @author Oleg Zhurakousky * @author Yaron Yamin * * @since 2.2 */ public class MongoDbMessageSource extends IntegrationObjectSupport implements MessageSource<Object> { private final Expression queryExpression; private volatile Expression collectionNameExpression = new LiteralExpression("data"); private volatile StandardEvaluationContext evaluationContext; private volatile MongoOperations mongoTemplate; private volatile MongoConverter mongoConverter; private volatile MongoDbFactory mongoDbFactory; private volatile boolean initialized = false; private volatile Class<?> entityClass = DBObject.class; private volatile boolean expectSingleResult = false; /** * Creates an instance with the provided {@link MongoDbFactory} and SpEL expression * which should resolve to a MongoDb 'query' string * (see http://www.mongodb.org/display/DOCS/Querying). * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. * @param mongoDbFactory The mongodb factory. * @param queryExpression The query expression. */ public MongoDbMessageSource(MongoDbFactory mongoDbFactory, Expression queryExpression) { Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null"); Assert.notNull(queryExpression, "'queryExpression' must not be null"); this.mongoDbFactory = mongoDbFactory; this.queryExpression = queryExpression; } /** * Creates an instance with the provided {@link MongoOperations} and SpEL expression * which should resolve to a Mongo 'query' string * (see http://www.mongodb.org/display/DOCS/Querying). * It assumes that the {@link MongoOperations} is fully initialized and ready to be used. * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. * @param mongoTemplate The mongo template. * @param queryExpression The query expression. */ public MongoDbMessageSource(MongoOperations mongoTemplate, Expression queryExpression) { Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); Assert.notNull(queryExpression, "'queryExpression' must not be null"); this.mongoTemplate = mongoTemplate; this.queryExpression = queryExpression; } /** * Allows you to set the type of the entityClass that will be passed to the * {@link MongoTemplate#find(Query, Class)} or {@link MongoTemplate#findOne(Query, Class)} * method. * Default is {@link DBObject}. * @param entityClass The entity class. */ public void setEntityClass(Class<?> entityClass) { Assert.notNull(entityClass, "'entityClass' must not be null"); this.entityClass = entityClass; } /** * Allows you to manage which find* method to invoke on {@link MongoTemplate}. * Default is 'false', which means the {@link #receive()} method will use * the {@link MongoTemplate#find(Query, Class)} method. If set to 'true', * {@link #receive()} will use {@link MongoTemplate#findOne(Query, Class)}, * and the payload of the returned {@link Message} will be the returned target Object of type * identified by {{@link #entityClass} instead of a List. * @param expectSingleResult true if a single result is expected. */ public void setExpectSingleResult(boolean expectSingleResult) { this.expectSingleResult = expectSingleResult; } /** * Sets the SpEL {@link Expression} that should resolve to a collection name * used by the {@link Query}. The resulting collection name will be included * in the {@link MongoHeaders#COLLECTION_NAME} header. * @param collectionNameExpression The collection name expression. */ public void setCollectionNameExpression(Expression collectionNameExpression) { Assert.notNull(collectionNameExpression, "'collectionNameExpression' must not be null"); this.collectionNameExpression = collectionNameExpression; } /** * Allows you to provide a custom {@link MongoConverter} used to assist in deserialization * data read from MongoDb. Only allowed if this instance was constructed with a * {@link MongoDbFactory}. * @param mongoConverter The mongo converter. */ public void setMongoConverter(MongoConverter mongoConverter) { Assert.isNull(this.mongoTemplate, "'mongoConverter' can not be set when instance was constructed with MongoTemplate"); this.mongoConverter = mongoConverter; } @Override public String getComponentType() { return "mongo:inbound-channel-adapter"; } @Override protected void onInit() throws Exception { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); if (typeLocator instanceof StandardTypeLocator) { //Register MongoDB query API package so FQCN can be avoided in query-expression. ((StandardTypeLocator) typeLocator).registerImport("org.springframework.data.mongodb.core.query"); } if (this.mongoTemplate == null) { this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mongoConverter); } this.initialized = true; } /** * Will execute a {@link Query} returning its results as the Message payload. * The payload can be either {@link List} of elements of objects of type * identified by {{@link #entityClass}, or a single element of type identified by {{@link #entityClass} * based on the value of {{@link #expectSingleResult} attribute which defaults to 'false' resulting * {@link Message} with payload of type {@link List}. The collection name used in the * query will be provided in the {@link MongoHeaders#COLLECTION_NAME} header. */ @Override public Message<Object> receive() { Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method"); Message<Object> message = null; Object value = this.queryExpression.getValue(this.evaluationContext); Assert.notNull(value, "'queryExpression' must not evaluate to null"); Query query; if (value instanceof String) { query = new BasicQuery((String) value); } else if (value instanceof Query) { query = ((Query) value); } else { throw new IllegalStateException("'queryExpression' must evaluate to String " + "or org.springframework.data.mongodb.core.query.Query"); } Assert.notNull(query, "'queryExpression' must not evaluate to null"); String collectionName = this.collectionNameExpression.getValue(this.evaluationContext, String.class); Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null"); Object result = null; if (this.expectSingleResult) { result = this.mongoTemplate. findOne(query, this.entityClass, collectionName); } else { List<?> results = this.mongoTemplate. find(query, this.entityClass, collectionName); if (!CollectionUtils.isEmpty(results)) { result = results; } } if (result != null) { message = this.getMessageBuilderFactory().withPayload(result) .setHeader(MongoHeaders.COLLECTION_NAME, collectionName) .build(); } Object holder = TransactionSynchronizationManager.getResource(this); if (holder != null) { Assert.isInstanceOf(IntegrationResourceHolder.class, holder); ((IntegrationResourceHolder) holder).addAttribute("mongoTemplate", this.mongoTemplate); } return message; } }