/*
* Copyright 2013 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.socialsignin.spring.data.dynamodb.repository.query;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
import org.socialsignin.spring.data.dynamodb.query.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
/**
* @author Michael Lavelle
*/
public abstract class AbstractDynamoDBQuery<T, ID extends Serializable> implements RepositoryQuery {
protected final DynamoDBOperations dynamoDBOperations;
private final DynamoDBQueryMethod<T, ID> method;
public AbstractDynamoDBQuery(DynamoDBOperations dynamoDBOperations, DynamoDBQueryMethod<T, ID> method) {
this.dynamoDBOperations = dynamoDBOperations;
this.method = method;
}
protected QueryExecution<T, ID> getExecution() {
if (method.isCollectionQuery() && !isSingleEntityResultsRestriction()) {
return new CollectionExecution();
}
else if (method.isSliceQuery() && !isSingleEntityResultsRestriction()) {
return new SlicedExecution(method.getParameters());
} else if (method.isPageQuery() && !isSingleEntityResultsRestriction()) {
return new PagedExecution(method.getParameters());
} else if (method.isModifyingQuery()) {
throw new UnsupportedOperationException("Modifying queries not yet supported");
} else if (isSingleEntityResultsRestriction()) {
return new SingleEntityLimitedExecution();
}
else {
return new SingleEntityExecution();
}
}
protected abstract Query<T> doCreateQuery(Object[] values);
protected abstract Query<Long> doCreateCountQuery(Object[] values,boolean pageQuery);
protected abstract boolean isCountQuery();
protected abstract Integer getResultsRestrictionIfApplicable();
protected abstract boolean isSingleEntityResultsRestriction();
protected Query<T> doCreateQueryWithPermissions(Object values[]) {
Query<T> query = doCreateQuery(values);
query.setScanEnabled(method.isScanEnabled());
return query;
}
protected Query<Long> doCreateCountQueryWithPermissions(Object values[],boolean pageQuery) {
Query<Long> query = doCreateCountQuery(values,pageQuery);
query.setScanCountEnabled(method.isScanCountEnabled());
return query;
}
private interface QueryExecution<T, ID extends Serializable> {
public Object execute(AbstractDynamoDBQuery<T, ID> query, Object[] values);
}
class CollectionExecution implements QueryExecution<T, ID> {
@Override
public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
if (getResultsRestrictionIfApplicable() != null)
{
return restrictMaxResultsIfNecessary(query.getResultList().iterator());
}
else return query.getResultList();
}
private List<T> restrictMaxResultsIfNecessary(Iterator<T> iterator) {
int processed = 0;
List<T> resultsPage = new ArrayList<T>();
while (iterator.hasNext() && processed < getResultsRestrictionIfApplicable()) {
resultsPage.add(iterator.next());
processed++;
}
return resultsPage;
}
}
/**
* Executes the {@link AbstractStringBasedJpaQuery} to return a
* {@link org.springframework.data.domain.Page} of entities.
*/
class PagedExecution implements QueryExecution<T, ID> {
private final Parameters<?, ?> parameters;
public PagedExecution(Parameters<?, ?> parameters) {
this.parameters = parameters;
}
private int scanThroughResults(Iterator<T> iterator, int resultsToScan) {
int processed = 0;
while (iterator.hasNext() && processed < resultsToScan) {
iterator.next();
processed++;
}
return processed;
}
private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
int processed = 0;
int toProcess = getResultsRestrictionIfApplicable() != null ? Math.min(pageSize,getResultsRestrictionIfApplicable()) : pageSize;
List<T> resultsPage = new ArrayList<T>();
while (iterator.hasNext() && processed < toProcess) {
resultsPage.add(iterator.next());
processed++;
}
return resultsPage;
}
@Override
public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
Pageable pageable = accessor.getPageable();
Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
List<T> results = query.getResultList();
return createPage(results, pageable,dynamoDBQuery,values);
}
private Page<T> createPage(List<T> allResults, Pageable pageable,AbstractDynamoDBQuery<T, ID> dynamoDBQuery,Object[] values) {
Iterator<T> iterator = allResults.iterator();
int processedCount = 0;
if (pageable.getOffset() > 0) {
processedCount = scanThroughResults(iterator, pageable.getOffset());
if (processedCount < pageable.getOffset())
return new PageImpl<T>(new ArrayList<T>());
}
List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
Query<Long> countQuery = dynamoDBQuery.doCreateCountQueryWithPermissions(values,true);
long count = countQuery.getSingleResult();
if (getResultsRestrictionIfApplicable() != null)
{
count = Math.min(count,getResultsRestrictionIfApplicable());
}
return new PageImpl<T>(results, pageable, count);
}
}
class SlicedExecution implements QueryExecution<T, ID> {
private final Parameters<?, ?> parameters;
public SlicedExecution(Parameters<?, ?> parameters) {
this.parameters = parameters;
}
private int scanThroughResults(Iterator<T> iterator, int resultsToScan) {
int processed = 0;
while (iterator.hasNext() && processed < resultsToScan) {
iterator.next();
processed++;
}
return processed;
}
private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
int processed = 0;
int toProcess = getResultsRestrictionIfApplicable() != null ? Math.min(pageSize,getResultsRestrictionIfApplicable()) : pageSize;
List<T> resultsPage = new ArrayList<T>();
while (iterator.hasNext() && processed < toProcess) {
resultsPage.add(iterator.next());
processed++;
}
return resultsPage;
}
@Override
public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
Pageable pageable = accessor.getPageable();
Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
List<T> results = query.getResultList();
return createSlice(results, pageable);
}
private Slice<T> createSlice(List<T> allResults, Pageable pageable) {
Iterator<T> iterator = allResults.iterator();
int processedCount = 0;
if (pageable.getOffset() > 0) {
processedCount = scanThroughResults(iterator, pageable.getOffset());
if (processedCount < pageable.getOffset())
return new SliceImpl<T>(new ArrayList<T>());
}
List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
// Scan ahead to retrieve the next page count
boolean hasMoreResults = scanThroughResults(iterator, 1) > 0;
if (getResultsRestrictionIfApplicable() != null && getResultsRestrictionIfApplicable().intValue() <= results.size()) hasMoreResults = false;
return new SliceImpl<T>(results, pageable, hasMoreResults);
}
}
class SingleEntityExecution implements QueryExecution<T, ID> {
@Override
public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
if (isCountQuery())
{
return dynamoDBQuery.doCreateCountQueryWithPermissions(values,false).getSingleResult();
}
else
{
return dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult();
}
}
}
class SingleEntityLimitedExecution implements QueryExecution<T, ID> {
@Override
public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
if (isCountQuery())
{
return dynamoDBQuery.doCreateCountQueryWithPermissions(values,false).getSingleResult();
}
else
{
List<T> resultList = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList();
return resultList.size() == 0 ? null : resultList.get(0);
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.query.RepositoryQuery#execute(java
* .lang.Object[])
*/
public Object execute(Object[] parameters) {
return getExecution().execute(this, parameters);
}
@Override
public DynamoDBQueryMethod<T, ID> getQueryMethod() {
return this.method;
}
}