/*
* Copyright 2010-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.cassandra.repository.query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingConverter;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultSetQuery;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.SingleEntityExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.StreamExecution;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Statement;
/**
* Base class for {@link RepositoryQuery} implementations for Cassandra.
*
* @author Mark Paluch
* @author John Blum
*/
public abstract class AbstractCassandraQuery implements RepositoryQuery {
protected static Logger log = LoggerFactory.getLogger(AbstractCassandraQuery.class);
private final CassandraQueryMethod queryMethod;
private final CassandraOperations operations;
private final EntityInstantiators instantiators;
/**
* Create a new {@link AbstractCassandraQuery} from the given {@link CassandraQueryMethod} and
* {@link CassandraOperations}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public AbstractCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations) {
Assert.notNull(queryMethod, "CassandraQueryMethod must not be null");
Assert.notNull(operations, "CassandraOperations must not be null");
this.queryMethod = queryMethod;
this.operations = operations;
this.instantiators = new EntityInstantiators();
}
/**
* @deprecated as of 1.5, {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type
* conversion.
*/
@Deprecated
public void setConversionService(ConversionService conversionService) {
throw new UnsupportedOperationException("setConversionService(ConversionService) is not supported anymore. "
+ "Please use CassandraMappingContext instead");
}
/**
* @deprecated as of 1.5, {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type
* conversion.
*/
@Deprecated
public ConversionService getConversionService() {
return getOperations().getConverter().getConversionService();
}
/* (non-Javadoc) */
protected EntityInstantiators getEntityInstantiators() {
return this.instantiators;
}
/* (non-Javadoc) */
protected CassandraOperations getOperations() {
return this.operations;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
@Override
public CassandraQueryMethod getQueryMethod() {
return this.queryMethod;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
public Object execute(Object[] parameters) {
CassandraParameterAccessor parameterAccessor = new ConvertingParameterAccessor(getOperations().getConverter(),
new CassandraParametersParameterAccessor(getQueryMethod(), parameters));
ResultProcessor resultProcessor =
getQueryMethod().getResultProcessor().withDynamicProjection(parameterAccessor);
Statement statement = createQuery(parameterAccessor);
CassandraQueryExecution queryExecution = getExecution(new ResultProcessingConverter(
resultProcessor, getOperations().getConverter().getMappingContext(), getEntityInstantiators()));
CassandraReturnedType returnedType = new CassandraReturnedType(resultProcessor.getReturnedType(),
getOperations().getConverter().getCustomConversions());
Class<?> resultType = (returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType());
return queryExecution.execute(statement, resultType);
}
/**
* Returns the execution instance to use.
*
* @param resultProcessing must not be {@literal null}. @return
*/
private CassandraQueryExecution getExecution(Converter<Object, Object> resultProcessing) {
return new ResultProcessingExecution(getExecutionToWrap(resultProcessing), resultProcessing);
}
private CassandraQueryExecution getExecutionToWrap(Converter<Object, Object> resultProcessing) {
if (getQueryMethod().isCollectionQuery()) {
return new CollectionExecution(getOperations());
} else if (getQueryMethod().isResultSetQuery()) {
return new ResultSetQuery(getOperations());
} else if (getQueryMethod().isStreamQuery()) {
return new StreamExecution(getOperations(), resultProcessing);
} else {
return new SingleEntityExecution(getOperations());
}
}
/**
<<<<<<< 5cbbf085712acc72951fc9627d0f80c85dbb1505
* Creates a string query using the given {@link ParameterAccessor}
=======
* @param resultSet
* @param declaredReturnType
* @param returnedUnwrappedObjectType
* @return
* @deprecated as of 1.5, {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type
* conversion.
*/
@Deprecated
public Object getCollectionOfEntity(ResultSet resultSet, Class<?> declaredReturnType,
Class<?> returnedUnwrappedObjectType) {
Collection<Object> results;
if (ClassUtils.isAssignable(SortedSet.class, declaredReturnType)) {
results = new TreeSet<>();
} else if (ClassUtils.isAssignable(Set.class, declaredReturnType)) {
results = new HashSet<>();
} else { // List.class, Collection.class, or array
results = new ArrayList<>();
}
CassandraConverter converter = getOperations().getConverter();
for (Row row : resultSet) {
results.add(converter.read(returnedUnwrappedObjectType, row));
}
return results;
}
/**
* @param resultSet
* @param type
* @return
* @deprecated as of 1.5, {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type
* conversion.
*/
@Deprecated
public Object getSingleEntity(ResultSet resultSet, Class<?> type) {
Object result = (resultSet.isExhausted() ? null : getOperations().getConverter().read(type, resultSet.one()));
warnIfMoreResults(resultSet);
return result;
}
private void warnIfMoreResults(ResultSet resultSet) {
if (log.isWarnEnabled() && !resultSet.isExhausted()) {
int count = 0;
while (resultSet.one() != null) {
count++;
}
log.warn("ignoring extra {} row{}", count, count == 1 ? "" : "s");
}
}
@Deprecated
protected void warnIfMoreResults(Iterator<Row> iterator) {
if (log.isWarnEnabled() && iterator.hasNext()) {
int count = 0;
while (iterator.hasNext()) {
count++;
iterator.next();
}
log.warn("ignoring extra {} row{}", count, count == 1 ? "" : "s");
}
}
/**
* Creates a {@link Statement} using the given {@link ParameterAccessor}
>>>>>>> DATACASS-343 - Introduce Query and Update objects.
*
* @param accessor must not be {@literal null}.
*/
protected abstract Statement createQuery(CassandraParameterAccessor accessor);
@RequiredArgsConstructor
private class CassandraReturnedType {
private final ReturnedType returnedType;
private final CustomConversions customConversions;
boolean isProjecting() {
if (!returnedType.isProjecting()) {
return false;
}
// Spring Data Cassandra allows List<Map<String, Object> and Map<String, Object> declarations
// on query methods so we don't want to let projection kick in
if (ClassUtils.isAssignable(Map.class, returnedType.getReturnedType())) {
return false;
}
// Type conversion using registered conversions is handled on template level
if (customConversions.hasCustomWriteTarget(returnedType.getReturnedType())) {
return false;
}
// Don't apply projection on Cassandra simple types
return !customConversions.isSimpleType(returnedType.getReturnedType());
}
Class<?> getDomainType() {
return returnedType.getDomainType();
}
Class<?> getReturnedType() {
return returnedType.getReturnedType();
}
}
}