/* * 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.Iterator; import java.util.List; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.core.query.Criteria; import org.springframework.data.cassandra.core.query.CriteriaDefinition; import org.springframework.data.cassandra.core.query.Query; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentPropertyPath; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.datastax.driver.core.querybuilder.Clause; /** * Custom query creator to create Cassandra criteria. * * @author Matthew Adams * @author Mark Paluch * @author John Blum */ class CassandraQueryCreator extends AbstractQueryCreator<Query, CriteriaDefinition> { private static final Logger LOG = LoggerFactory.getLogger(CassandraQueryCreator.class); private final CassandraMappingContext mappingContext; private final QueryBuilder queryBuilder = new QueryBuilder(); /** * Create a new {@link CassandraQueryCreator} from the given {@link PartTree}, {@link ConvertingParameterAccessor} and * {@link MappingContext}. * * @param tree must not be {@literal null}. * @param accessor must not be {@literal null}. * @param mappingContext must not be {@literal null}. */ public CassandraQueryCreator(PartTree tree, CassandraParameterAccessor accessor, CassandraMappingContext mappingContext) { super(tree, accessor); Assert.notNull(mappingContext, "CassandraMappingContext must not be null"); this.mappingContext = mappingContext; } /** * Returns the {@link CassandraMappingContext} used by this template to access mapping meta-data used to * store (map) object to Cassandra tables. * * @return the {@link CassandraMappingContext} used by this template. * @see org.springframework.data.cassandra.mapping.CassandraMappingContext */ protected CassandraMappingContext getMappingContext() { return this.mappingContext; } /** * Returns the {@link QueryBuilder} used to construct Cassandra CQL queries. * * @return the {@link QueryBuilder} used to construct Cassandra CQL queries. */ protected QueryBuilder getQueryBuilder() { return this.queryBuilder; } /* (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) */ @Override protected CriteriaDefinition create(Part part, Iterator<Object> iterator) { PersistentPropertyPath<CassandraPersistentProperty> path = getMappingContext().getPersistentPropertyPath(part.getProperty()); CassandraPersistentProperty property = path.getLeafProperty(); return from(part, property, Criteria.where(path.toDotPath()), (PotentiallyConvertingIterator) iterator); } /* (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) */ @Override protected CriteriaDefinition and(Part part, CriteriaDefinition base, Iterator<Object> iterator) { if (base == null) { return getQueryBuilder().and(create(part, iterator)); } getQueryBuilder().and(base); return create(part, iterator); } /* * Cassandra does not support OR queries. * * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object) */ @Override protected CriteriaDefinition or(CriteriaDefinition base, CriteriaDefinition criteria) { throw new InvalidDataAccessApiUsageException("Cassandra does not support an OR operator"); } /* (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort) */ @Override protected Query complete(CriteriaDefinition criteria, Sort sort) { if (criteria != null) { getQueryBuilder().and(criteria); } Query query = getQueryBuilder().create(sort); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Created query [%s]", query)); } return query; } private CriteriaDefinition from(Part part, CassandraPersistentProperty property, Criteria where, PotentiallyConvertingIterator parameters) { Type type = part.getType(); switch (type) { case AFTER: case GREATER_THAN: return where.gt(parameters.nextConverted(property)); case GREATER_THAN_EQUAL: return where.gte(parameters.nextConverted(property)); case BEFORE: case LESS_THAN: return where.lt(parameters.nextConverted(property)); case LESS_THAN_EQUAL: return where.lte(parameters.nextConverted(property)); case IN: return where.in(nextAsArray(property, parameters)); case LIKE: case STARTING_WITH: case ENDING_WITH: return where.like(like(type, parameters.nextConverted(property))); case CONTAINING: return containing(where, property, parameters.nextConverted(property)); case TRUE: return where.is(true); case FALSE: return where.is(false); case SIMPLE_PROPERTY: return where.is(parameters.nextConverted(property)); default: throw new InvalidDataAccessApiUsageException( String.format("Unsupported keyword [%s] in part [%s]", type, part)); } } private CriteriaDefinition containing(Criteria where, CassandraPersistentProperty property, Object bindableValue) { if (property.isCollectionLike() || property.isMapLike()) { return where.contains(bindableValue); } return where.like(like(Type.CONTAINING, bindableValue)); } private Object like(Type type, Object value) { if (value != null) { switch (type) { case LIKE: return value; case CONTAINING: return "%" + value + "%"; case STARTING_WITH: return value + "%"; case ENDING_WITH: return "%" + value; } throw new IllegalArgumentException(String.format("Part Type [%s] not supported with like queries", type)); } return null; } private Object[] nextAsArray(CassandraPersistentProperty property, PotentiallyConvertingIterator iterator) { Object next = iterator.nextConverted(property); if (next instanceof Collection) { return ((Collection<?>) next).toArray(); } else if (next.getClass().isArray()) { return (Object[]) next; } return new Object[] { next }; } /** * Where clause builder. Collects {@link Clause clauses} and builds the where-clause depending on the WHERE type. * * @author Mark Paluch */ static class QueryBuilder { private List<CriteriaDefinition> criterias = new ArrayList<>(); CriteriaDefinition and(CriteriaDefinition clause) { criterias.add(clause); return clause; } Query create(Sort sort) { Query query = Query.query(criterias); return query.sort(sort); } } }