/*
* Copyright 2016-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.data.mongodb.repository.query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.CollectionExecution;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.DeleteExecution;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.GeoNearExecution;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingConverter;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingExecution;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.SingleEntityExecution;
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.TailExecution;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.util.Assert;
/**
* Base class for reactive {@link RepositoryQuery} implementations for MongoDB.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
private final ReactiveMongoQueryMethod method;
private final ReactiveMongoOperations operations;
private final EntityInstantiators instantiators;
/**
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
* {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations) {
Assert.notNull(method, "MongoQueryMethod must not be null!");
Assert.notNull(operations, "ReactiveMongoOperations must not be null!");
this.method = method;
this.operations = operations;
this.instantiators = new EntityInstantiators();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
public MongoQueryMethod getQueryMethod() {
return method;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
public Object execute(Object[] parameters) {
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
: execute(new MongoParametersParameterAccessor(method, parameters));
}
@SuppressWarnings("unchecked")
private Object executeDeferred(Object[] parameters) {
ReactiveMongoParameterAccessor parameterAccessor = new ReactiveMongoParameterAccessor(method, parameters);
if (getQueryMethod().isCollectionQuery()) {
return Flux.defer(() -> (Publisher<Object>) execute(parameterAccessor));
}
return Mono.defer(() -> (Mono<Object>) execute(parameterAccessor));
}
private Object execute(MongoParameterAccessor parameterAccessor) {
Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor));
applyQueryMetaAttributesWhenPresent(query);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
String collection = method.getEntityInformation().getCollectionName();
ReactiveMongoQueryExecution execution = getExecution(query, parameterAccessor,
new ResultProcessingConverter(processor, operations, instantiators));
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
}
/**
* Returns the execution instance to use.
*
* @param query must not be {@literal null}.
* @param accessor must not be {@literal null}.
* @param resultProcessing must not be {@literal null}.
* @return
*/
private ReactiveMongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor,
Converter<Object, Object> resultProcessing) {
return new ResultProcessingExecution(getExecutionToWrap(accessor), resultProcessing);
}
private ReactiveMongoQueryExecution getExecutionToWrap(MongoParameterAccessor accessor) {
if (isDeleteQuery()) {
return new DeleteExecution(operations, method);
} else if (method.isGeoNearQuery()) {
return new GeoNearExecution(operations, accessor, method.getReturnType());
} else if (isTailable(method)) {
return new TailExecution(operations, accessor.getPageable());
} else if (method.isCollectionQuery()) {
return new CollectionExecution(operations, accessor.getPageable());
} else {
return new SingleEntityExecution(operations, isCountQuery());
}
}
private boolean isTailable(MongoQueryMethod method) {
return method.getTailableAnnotation() != null;
}
Query applyQueryMetaAttributesWhenPresent(Query query) {
if (method.hasQueryMetaAttributes()) {
query.setMeta(method.getQueryMetaAttributes());
}
return query;
}
/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
* triggered.
*
* @param accessor must not be {@literal null}.
* @return
*/
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
}
/**
* Creates a {@link Query} instance using the given {@link ParameterAccessor}
*
* @param accessor must not be {@literal null}.
* @return
*/
protected abstract Query createQuery(ConvertingParameterAccessor accessor);
/**
* Returns whether the query should get a count projection applied.
*
* @return
*/
protected abstract boolean isCountQuery();
/**
* Return weather the query should delete matching documents.
*
* @return
* @since 1.5
*/
protected abstract boolean isDeleteQuery();
}