DATAGEODE-263 - Add Paging framework and infrastructure supporting classes.
This commit is contained in:
@@ -268,7 +268,7 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
public <E> SelectResults<E> query(String query) {
|
||||
|
||||
try {
|
||||
return this.getRegion().query(query);
|
||||
return getRegion().query(query);
|
||||
}
|
||||
catch (IndexInvalidException | QueryInvalidException cause) {
|
||||
throw convertGemFireQueryException(cause);
|
||||
@@ -291,21 +291,26 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E> SelectResults<E> find(String queryString, Object... arguments) throws InvalidDataAccessApiUsageException {
|
||||
public <E> SelectResults<E> find(String query, Object... arguments) throws InvalidDataAccessApiUsageException {
|
||||
|
||||
try {
|
||||
|
||||
QueryService queryService = resolveQueryService(getRegion());
|
||||
Query query = queryService.newQuery(queryString);
|
||||
Object result = query.execute(arguments);
|
||||
|
||||
Query compiledQuery = queryService.newQuery(query);
|
||||
|
||||
Object result = compiledQuery.execute(arguments);
|
||||
|
||||
if (result instanceof SelectResults) {
|
||||
return (SelectResults<E>) result;
|
||||
}
|
||||
else {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"The result from executing query [%1$s] was not an instance of SelectResults [%2$s]",
|
||||
queryString, result));
|
||||
|
||||
String message =
|
||||
String.format("The result from executing query [%1$s] was not an instance of SelectResults [%2$s]",
|
||||
query, result);
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(message);
|
||||
}
|
||||
}
|
||||
catch (IndexInvalidException | QueryInvalidException cause) {
|
||||
@@ -329,13 +334,15 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T findUnique(String queryString, Object... params) throws InvalidDataAccessApiUsageException {
|
||||
public <T> T findUnique(String query, Object... arguments) throws InvalidDataAccessApiUsageException {
|
||||
|
||||
try {
|
||||
|
||||
QueryService queryService = resolveQueryService(getRegion());
|
||||
Query query = queryService.newQuery(queryString);
|
||||
Object result = query.execute(params);
|
||||
|
||||
Query compiledQuery = queryService.newQuery(query);
|
||||
|
||||
Object result = compiledQuery.execute(arguments);
|
||||
|
||||
if (result instanceof SelectResults) {
|
||||
|
||||
@@ -347,8 +354,11 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
result = results.get(0);
|
||||
}
|
||||
else {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"The result returned from query [%1$s]) was not unique [%2$s]", queryString, result));
|
||||
|
||||
String message = String.format("The result returned from query [%1$s]) was not unique [%2$s]",
|
||||
query, result);
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
import org.apache.geode.cache.query.types.CollectionType;
|
||||
import org.apache.geode.cache.query.types.ObjectType;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An abstract base class implementation of Apache Geode's {@link SelectResults} interface and Java {@link Collection}
|
||||
* interface, which delegates to, and is backed by a given, required {@link SelectResults} instance.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Collection
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class AbstractSelectResults<T> implements SelectResults<T> {
|
||||
|
||||
private final SelectResults<T> selectResults;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SelectResults} initialized with the given, required {@link SelectResults}
|
||||
* instance backing this base class.
|
||||
*
|
||||
* @param selectResults {@link SelectResults} delegate backing this implementation; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link SelectResults} is {@literal null}.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
public AbstractSelectResults(@NonNull SelectResults<T> selectResults) {
|
||||
|
||||
Assert.notNull(selectResults, "SelectResults must not be null");
|
||||
|
||||
this.selectResults = selectResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured, underlying {@link SelectResults} used as the delegate
|
||||
* backing this {@link SelectResults} implementation.
|
||||
*
|
||||
* @return the configured, underlying {@link SelectResults}.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
protected @NonNull SelectResults<T> getSelectResults() {
|
||||
return this.selectResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public List<T> asList() {
|
||||
return getSelectResults().asList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Set<T> asSet() {
|
||||
return getSelectResults().asSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public CollectionType getCollectionType() {
|
||||
return getSelectResults().getCollectionType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean isModifiable() {
|
||||
return getSelectResults().isModifiable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public int occurrences(T result) {
|
||||
return getSelectResults().occurrences(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setElementType(ObjectType objectType) {
|
||||
getSelectResults().setElementType(objectType);
|
||||
}
|
||||
|
||||
// java.util.Collection interface methods
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean add(T result) {
|
||||
return getSelectResults().add(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> results) {
|
||||
return getSelectResults().addAll(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
getSelectResults().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object result) {
|
||||
return getSelectResults().contains(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> results) {
|
||||
return getSelectResults().containsAll(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return getSelectResults().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return getSelectResults().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object result) {
|
||||
return getSelectResults().remove(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> results) {
|
||||
return getSelectResults().removeAll(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> results) {
|
||||
return getSelectResults().retainAll(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return getSelectResults().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return getSelectResults().toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public <E> E[] toArray(E[] array) {
|
||||
return getSelectResults().toArray(array);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.gemfire.repository.query;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
|
||||
import org.springframework.data.gemfire.repository.Query;
|
||||
@@ -38,11 +37,13 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Apache Geode specific {@link QueryMethod} implementation.
|
||||
* {@link QueryMethod} implementation for Apache Geode.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see java.lang.reflect.Method
|
||||
* @see org.springframework.data.gemfire.repository.Query
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
public class GemfireQueryMethod extends QueryMethod {
|
||||
@@ -53,6 +54,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
@@ -117,27 +119,6 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link Method} backing this {@link QueryMethod} is a {@link Pageable} {@link Method},
|
||||
* which requires special logic given Apache Geode does not support pagination since it has no concept of a
|
||||
* {@literal Database Cursor}.
|
||||
*
|
||||
* @param method {@literal query} {@link Method} to be evaluate.
|
||||
* @return a boolean value indicating whether the {@link Method} has a parameter of type {@link Pageable}.
|
||||
* @see java.lang.reflect.Method#getParameterTypes()
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
private boolean isPageableQueryMethod(@NonNull Method method) {
|
||||
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
if (Pageable.class.isAssignableFrom(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Method} reference on which this {@link QueryMethod} is based.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,9 @@ import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for Apache Geode specific {@link RepositoryQuery} implementations.
|
||||
*
|
||||
@@ -35,6 +38,8 @@ import org.springframework.util.Assert;
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final GemfireQueryMethod queryMethod;
|
||||
|
||||
private QueryPostProcessor<Repository, String> queryPostProcessor = ProvidedQueryPostProcessor.IDENTITY;
|
||||
@@ -73,6 +78,16 @@ public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
return (GemfireQueryMethod) getQueryMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured SLF4J {@link Logger} used log statements.
|
||||
*
|
||||
* @return the configured SLF4J {@link Logger}.
|
||||
* @see org.slf4j.Logger
|
||||
*/
|
||||
protected @NonNull Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link Repository} {@link QueryMethod} modeling the data store query.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link QueryString} implementation handling {@literal paging} functionality and behavior.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class PagedQueryString extends QueryString {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link PagedQueryString} from an existing, {@literal non-null}
|
||||
* {@link QueryString}.
|
||||
*
|
||||
* @param queryString {@link QueryString} on which the {@link PagedQueryString} will be based.
|
||||
* @return a new instance of {@link PagedQueryString} initialized with the {@literal OQL query}
|
||||
* from the given {@link QueryString}.
|
||||
* @throws IllegalArgumentException if {@link QueryString} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
* @see #of(String)
|
||||
*/
|
||||
public static PagedQueryString of(@NonNull QueryString queryString) {
|
||||
|
||||
Assert.notNull(queryString, "QueryString must not be null");
|
||||
|
||||
return of(queryString.getQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link PagedQueryString} initialized with
|
||||
* the given {@literal OQL-based query}.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query statement.
|
||||
* @return a new instance of {@link PagedQueryString} initialized with the given {@literal OQL-based query}.
|
||||
* @throws IllegalArgumentException if the {@link String OQL query} is {@literal null} or {@literal empty}.
|
||||
* @see #PagedQueryString(String)
|
||||
*/
|
||||
public static PagedQueryString of(@NonNull String query) {
|
||||
return new PagedQueryString(query);
|
||||
}
|
||||
|
||||
private GemfireQueryMethod queryMethod;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link PagedQueryString} initialized with the given {@literal OQL-based query}.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query statement.
|
||||
* @throws IllegalArgumentException if the {@link String OQL query} is {@literal null} or {@literal empty}.
|
||||
*/
|
||||
public PagedQueryString(@NonNull String query) {
|
||||
super(query);
|
||||
}
|
||||
|
||||
protected Optional<GemfireQueryMethod> getQueryMethod() {
|
||||
return Optional.ofNullable(this.queryMethod);
|
||||
}
|
||||
|
||||
public PagedQueryString withQueryMethod(GemfireQueryMethod queryMethod) {
|
||||
this.queryMethod = queryMethod;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.gemfire.repository.query.support.PagingUtils;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An Apache Geode {@link SelectResults} implementation with support for {@literal Paging}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.gemfire.repository.query.AbstractSelectResults
|
||||
* @see org.springframework.data.gemfire.repository.query.support.PagingUtils
|
||||
* @see org.springframework.data.util.Lazy
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class PagedSelectResults<T> extends AbstractSelectResults<T> {
|
||||
|
||||
protected static final String NON_NULL_PAGEABLE_MESSAGE = "Pageable must not be null";
|
||||
|
||||
private Lazy<List<T>> pagedList;
|
||||
|
||||
private Pageable pageRequest;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link PagedSelectResults} initialized with the given, required
|
||||
* {@link SelectResults} and {@link Pageable} object encapsulating the details of the requested page.
|
||||
*
|
||||
* @param selectResults {@link SelectResults} to wrap; must not be {@literal null}.
|
||||
* @param pageable {@link Pageable} object encapsulating the details of the requested page;
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the {@link SelectResults} or the {@link Pageable} object is {@literal null}.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
public PagedSelectResults(@NonNull SelectResults<T> selectResults, @NonNull Pageable pageable) {
|
||||
|
||||
super(selectResults);
|
||||
|
||||
Assert.notNull(pageable, NON_NULL_PAGEABLE_MESSAGE);
|
||||
|
||||
this.pageRequest = pageable;
|
||||
this.pagedList = newLazyPagedList();
|
||||
}
|
||||
|
||||
// WARNING newLazyPagedList() allows the `this` reference to escape when called inside the constructor,
|
||||
// but this class makes no Thread-safety guarantees either.
|
||||
private Lazy<List<T>> newLazyPagedList() {
|
||||
return Lazy.of(() -> PagingUtils.getPagedList(getSelectResults().asList(), getPageRequest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Pageable} object encapsulating the details of the requested page.
|
||||
*
|
||||
* @return the {@link Pageable} object encapsulating the details of the requested page.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
protected @NonNull Pageable getPageRequest() {
|
||||
return this.pageRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Set<T> asSet() {
|
||||
return new HashSet<>(asList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public List<T> asList() {
|
||||
return this.pagedList.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return Collections.unmodifiableList(asList()).iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return asList().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to allow a new {@link Pageable page request} in order to get a different page of results
|
||||
* from the underlying {@link SelectResults}.
|
||||
*
|
||||
* @param pageRequest {@link Pageable} object encapsulating the details of the requested page;
|
||||
* must not be {@literal null}.
|
||||
* @return this {@link PagedSelectResults}.
|
||||
* @throws IllegalArgumentException if {@link Pageable} is {@literal null}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
public PagedSelectResults<T> with(@NonNull Pageable pageRequest) {
|
||||
|
||||
Assert.notNull(pageRequest, NON_NULL_PAGEABLE_MESSAGE);
|
||||
|
||||
this.pageRequest = pageRequest;
|
||||
this.pagedList = newLazyPagedList();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
QueryString query = newQueryString(queryMethod, getPartTree(), arguments);
|
||||
|
||||
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(query, queryMethod, getTemplate());
|
||||
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(queryMethod, query, getTemplate());
|
||||
|
||||
return repositoryQuery.execute(prepareStringParameters(arguments));
|
||||
}
|
||||
@@ -115,8 +115,8 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
return queryCreator.createQuery(parameterAccessor.getSort());
|
||||
}
|
||||
|
||||
private GemfireRepositoryQuery newRepositoryQuery(QueryString query,
|
||||
GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
private GemfireRepositoryQuery newRepositoryQuery(GemfireQueryMethod queryMethod,
|
||||
QueryString query, GemfireTemplate template) {
|
||||
|
||||
StringBasedGemfireRepositoryQuery repositoryQuery =
|
||||
new StringBasedGemfireRepositoryQuery(query.toString(), queryMethod, template);
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.gemfire.repository.query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -28,14 +29,19 @@ import org.springframework.data.gemfire.repository.query.support.OqlKeyword;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link QueryString} is a utility class used to construct Apache Geode OQL query statements.
|
||||
* {@link QueryString} is a base class used to construct and model syntactically valid Apache Geode
|
||||
* OQL query statements.
|
||||
*
|
||||
* This is an internal class used by the SDG {@link Repository} infrastructure extension
|
||||
* {@link QueryString} uses {@link Pattern} based recognition and {@link Matcher matching} to parse and modify
|
||||
* the OQL query statement.
|
||||
*
|
||||
* This is an internal class used by the SDG {@link Repository} infrastructure extension.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
@@ -45,6 +51,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlKeyword
|
||||
* @see org.springframework.data.repository.Repository
|
||||
*/
|
||||
public class QueryString {
|
||||
|
||||
@@ -55,27 +62,38 @@ public class QueryString {
|
||||
protected static final Pattern TRACE_PATTERN = Pattern.compile("<TRACE>");
|
||||
|
||||
// OQL Query Templates
|
||||
private static final String HINTS_OQL_TEMPLATE = "<HINT %1$s> %2$s";
|
||||
private static final String IMPORT_OQL_TEMPLATE = "IMPORT %1$s; %2$s";
|
||||
private static final String LIMIT_OQL_TEMPLATE = "%1$s LIMIT %2$d";
|
||||
private static final String SELECT_OQL_TEMPLATE = "SELECT %1$s FROM /%2$s";
|
||||
private static final String TRACE_OQL_TEMPLATE = "<TRACE> %1$s";
|
||||
protected static final String HINTS_OQL_TEMPLATE = "<HINT %1$s> %2$s";
|
||||
protected static final String IMPORT_OQL_TEMPLATE = "IMPORT %1$s; %2$s";
|
||||
protected static final String LIMIT_OQL_TEMPLATE = "%1$s LIMIT %2$d";
|
||||
protected static final String SELECT_OQL_TEMPLATE = "SELECT %1$s FROM /%2$s";
|
||||
protected static final String TRACE_OQL_TEMPLATE = "<TRACE> %1$s";
|
||||
|
||||
// OQL Query Regular Expression Patterns
|
||||
private static final String IN_PATTERN = "(?<=IN (SET|LIST) )\\$\\d";
|
||||
private static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d";
|
||||
private static final String REGION_PATTERN = "\\/(\\/?\\w)+";
|
||||
protected static final String COUNT_PROJECTION = "count(*)";
|
||||
protected static final String IN_PATTERN = "(?<=IN (SET|LIST) )\\$\\d";
|
||||
protected static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d";
|
||||
protected static final String REGION_PATTERN = "\\/(\\/?\\w)+";
|
||||
protected static final String STAR_PROJECTION = "*";
|
||||
|
||||
private static final String COUNT_QUERY = "count(*)";
|
||||
private static final String STAR_QUERY = "*";
|
||||
/**
|
||||
* @deprecated use {@link #COUNT_PROJECTION}.
|
||||
*/
|
||||
@Deprecated
|
||||
protected static final String COUNT_QUERY = COUNT_PROJECTION;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #STAR_PROJECTION}.
|
||||
*/
|
||||
@Deprecated
|
||||
protected static final String STAR_QUERY = STAR_PROJECTION;
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link QueryString} initialized with
|
||||
* the given {@link String OQL query}.
|
||||
* the given {@link String OQL query statement}.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query.
|
||||
* @return a new {@link QueryString} initialized with the given {@link String query}.
|
||||
* @throws IllegalArgumentException if {@link String query} is not specified.
|
||||
* @throws IllegalArgumentException if {@link String query} is {@literal null} or {@literal empty}.
|
||||
* @see #QueryString(String)
|
||||
*/
|
||||
public static QueryString of(@NonNull String query) {
|
||||
@@ -83,8 +101,8 @@ public class QueryString {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link QueryString} initialized with
|
||||
* the given {@link Class domain type} for which the {@link String OQL query} will be created.
|
||||
* Factory method used to construct a new instance of {@link QueryString} initialized with the given
|
||||
* {@link Class application domain model type} from which the {@link String OQL query} will be created.
|
||||
*
|
||||
* @param domainType {@link Class application domain model type} for which the {@link String OQL query}
|
||||
* will be created.
|
||||
@@ -98,7 +116,7 @@ public class QueryString {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link QueryString} that creates an {@link String OQL query}
|
||||
* counting objects of the specified {@link Class application domain model type}.
|
||||
* to count the number of objects of the specified {@link Class application domain model type}.
|
||||
*
|
||||
* @param domainType {@link Class application domain model type} for which the OQL query will be created.
|
||||
* @return a new count {@link QueryString}.
|
||||
@@ -109,17 +127,54 @@ public class QueryString {
|
||||
return new QueryString(domainType, true);
|
||||
}
|
||||
|
||||
static String asQuery(Class<?> domainType, boolean isCountQuery) {
|
||||
return String.format(SELECT_OQL_TEMPLATE, isCountQuery ? COUNT_QUERY : STAR_QUERY,
|
||||
validateDomainType(domainType).getSimpleName());
|
||||
/**
|
||||
* Null-safe method used to extract {@literal digits} from the given {@link String} value as a whole number.
|
||||
*
|
||||
* @param value {@link String} to evaluate.
|
||||
* @return the {@literal digits} extracted from the give {@link String} value as a whole number
|
||||
* or an {@link String#isEmpty() empty String} if the given {@link String} is {@literal null}, {@literal empty}
|
||||
* or contains no {@literal digits}.
|
||||
* @see java.lang.String
|
||||
*/
|
||||
protected static String getDigitsOnly(@Nullable String value) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (StringUtils.hasText(value)) {
|
||||
for (char c : value.toCharArray()) {
|
||||
if (Character.isDigit(c)) {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static <T> Class<T> validateDomainType(Class<T> domainType) {
|
||||
static String asQuery(Class<?> domainType, boolean isCountQuery) {
|
||||
return String.format(SELECT_OQL_TEMPLATE, resolveProjection(isCountQuery), resolveFrom(domainType));
|
||||
}
|
||||
|
||||
static String resolveFrom(@NonNull Class<?> domainType) {
|
||||
|
||||
return Optional.of(validateDomainType(domainType))
|
||||
.filter(it -> it.isAnnotationPresent(org.springframework.data.gemfire.mapping.annotation.Region.class))
|
||||
.map(it -> it.getAnnotation(org.springframework.data.gemfire.mapping.annotation.Region.class))
|
||||
.map(it -> it.value())
|
||||
.filter(StringUtils::hasText)
|
||||
.orElseGet(() -> domainType.getSimpleName());
|
||||
}
|
||||
|
||||
static @NonNull String resolveProjection(boolean isCountQuery) {
|
||||
return isCountQuery ? COUNT_PROJECTION : STAR_PROJECTION;
|
||||
}
|
||||
|
||||
static @NonNull <T> Class<T> validateDomainType(@NonNull Class<T> domainType) {
|
||||
Assert.notNull(domainType, "Domain type is required");
|
||||
return domainType;
|
||||
}
|
||||
|
||||
static String validateQuery(String query) {
|
||||
static @NonNull String validateQuery(@NonNull String query) {
|
||||
Assert.hasText(query, String.format("Query [%s] is required", query));
|
||||
return query;
|
||||
}
|
||||
@@ -140,7 +195,7 @@ public class QueryString {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link QueryString} initialized with the given
|
||||
* {@link Class application domain model type}, which is used to construct an OQL {@literal SELECT} query statement.
|
||||
* {@link Class application domain model type} used to construct an OQL {@literal SELECT} query statement.
|
||||
*
|
||||
* @param domainType {@link Class application domain model type} to query; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the {@link Class application domain model type} is {@literal null}.
|
||||
@@ -164,37 +219,119 @@ public class QueryString {
|
||||
* @see #asQuery(Class, boolean)
|
||||
* @see #QueryString(String)
|
||||
*/
|
||||
public QueryString(Class<?> domainType, boolean asCountQuery) {
|
||||
public QueryString(@NonNull Class<?> domainType, boolean asCountQuery) {
|
||||
this(asQuery(domainType, asCountQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the {@literal SELECT query} with a {@literal SELECT DISTINCT query} if the {@link String query}
|
||||
* is not already distinct; i.e. does not contain the {@literal DISTINCT} keyword.
|
||||
* Determines whether a {@literal LIMIT} is present in the OQL query.
|
||||
*
|
||||
* @return a {@literal SELECT DISTINCT query} if {@link String query} does not contain
|
||||
* the {@literal DISTINCT} keyword.
|
||||
* @see java.lang.String#replaceFirst(String, String)
|
||||
* @see #asDistinct(String)
|
||||
* @return a boolean value determining whether a {@literal LIMIT} is present in the OQL query.
|
||||
* @see #getLimit()
|
||||
*/
|
||||
public QueryString asDistinct() {
|
||||
return QueryString.of(asDistinct(this.query));
|
||||
public boolean isLimited() {
|
||||
return LIMIT_PATTERN.matcher(getQuery()).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the {@literal SELECT query} with a {@literal SELECT DISTINCT query} if the {@link String query}
|
||||
* is not already distinct; i.e. does not contain the {@literal DISTINCT} keyword.
|
||||
* Returns the parameter indexes used in this query.
|
||||
*
|
||||
* @param query {@link String} containing the {@link String query} to evaluate.
|
||||
* @return a {@literal SELECT DISTINCT query} if {@link String query} does not contain
|
||||
* the {@literal DISTINCT} keyword.
|
||||
* @return the parameter indexes used in this query or an empty {@link Iterable} if no parameter indexes are used.
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
public Iterable<Integer> getInParameterIndexes() {
|
||||
|
||||
Pattern pattern = Pattern.compile(IN_PARAMETER_PATTERN);
|
||||
|
||||
Matcher matcher = pattern.matcher(getQuery());
|
||||
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
indexes.add(Integer.parseInt(matcher.group()));
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@literal LIMIT} number.
|
||||
*
|
||||
* Use {@link #isLimited()} to determine whether the {@link String OQL query statement} has a {@literal LIMIT}.
|
||||
*
|
||||
* @return an {@link Integer} value containing the {@literal LIMIT} number or {@link Integer#MAX_VALUE}
|
||||
* if the {@link String OQL query statement} is not {@link #isLimited() limited}.
|
||||
* @see #isLimited()
|
||||
*/
|
||||
public int getLimit() {
|
||||
|
||||
String query = getQuery();
|
||||
|
||||
Matcher matcher = LIMIT_PATTERN.matcher(query);
|
||||
|
||||
if (matcher.find()) {
|
||||
|
||||
int startIndex = matcher.start();
|
||||
int endIndex = matcher.end();
|
||||
|
||||
String limit = query.substring(startIndex, endIndex);
|
||||
|
||||
return Integer.parseInt(getDigitsOnly(limit));
|
||||
}
|
||||
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String OQL query statement} from which this {@link QueryString} was constructed.
|
||||
*
|
||||
* @return the {@link String OQL query}; never {@literal null} or {@literal empty}.
|
||||
*/
|
||||
protected @NonNull String getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to adjust the {@literal LIMIT} of the {@link String OQL query} to use the new,
|
||||
* given {@link Integer LIMIT}.
|
||||
*
|
||||
* @param limit {@link Integer} value specifying the new query {@literal LIMIT}.
|
||||
* @return a new {@link QueryString} with the adjusted query {@literal LIMIT}.
|
||||
* @see #withLimit(Integer)
|
||||
*/
|
||||
public QueryString adjustLimit(@Nullable Integer limit) {
|
||||
|
||||
return limit != null
|
||||
? QueryString.of(LIMIT_PATTERN.matcher(getQuery()).replaceAll("").trim()).withLimit(limit)
|
||||
: this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an OQL {@literal SELECT} query with an OQL {@literal SELECT DISTINCT} query if the {@link String query}
|
||||
* is not already {@literal distinct}; i.e. does not contain the {@literal DISTINCT} OQL keyword.
|
||||
*
|
||||
* @return a {@literal SELECT DISTINCT} {@link QueryString query} if the {@link String query} does not contain
|
||||
* the {@literal DISTINCT} OQL keyword.
|
||||
* @see #asDistinct(String)
|
||||
*/
|
||||
public QueryString asDistinct() {
|
||||
return QueryString.of(asDistinct(getQuery()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an OQL {@literal SELECT} query with an OQL {@literal SELECT DISTINCT} query if the {@link String query}
|
||||
* is not already {@literal distinct}; i.e. does not contain the {@literal DISTINCT} OQL keyword.
|
||||
*
|
||||
* @param query {@link String} containing the query to evaluate.
|
||||
* @return a {@literal SELECT DISTINCT} {@link String query} if the {@link String query} does not contain
|
||||
* the {@literal DISTINCT} OQL keyword.
|
||||
* @see java.lang.String#replaceFirst(String, String)
|
||||
*/
|
||||
String asDistinct(String query) {
|
||||
|
||||
return query.contains(OqlKeyword.DISTINCT.getKeyword()) ? query
|
||||
: query.replaceFirst(OqlKeyword.SELECT.getKeyword(),
|
||||
String.format("%1$s %2$s", OqlKeyword.SELECT.getKeyword(), OqlKeyword.DISTINCT.getKeyword()));
|
||||
String.format("%1$s %2$s", OqlKeyword.SELECT.getKeyword(), OqlKeyword.DISTINCT.getKeyword()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +344,7 @@ public class QueryString {
|
||||
public QueryString bindIn(Collection<?> values) {
|
||||
|
||||
if (!CollectionUtils.nullSafeIsEmpty(values)) {
|
||||
return QueryString.of(this.query.replaceFirst(IN_PATTERN, String.format("(%s)",
|
||||
return QueryString.of(getQuery().replaceFirst(IN_PATTERN, String.format("(%s)",
|
||||
StringUtils.collectionToDelimitedString(values, ", ", "'", "'"))));
|
||||
}
|
||||
|
||||
@@ -218,37 +355,24 @@ public class QueryString {
|
||||
* Replaces the {@link Class domain classes} referenced inside the current {@link String query}
|
||||
* with the given {@link Region}.
|
||||
*
|
||||
* @param domainType {@link Class type} of the persistent entity to query; must not be {@literal null}.
|
||||
* @param region {@link Region} to query; must not be {@literal null}.
|
||||
* @param domainType {@link Class type} of the persistent entity to query; must not be {@literal null}.
|
||||
* @return a new {@link QueryString} with an OQL {@literal SELECT statement} having a {@literal FROM clause}
|
||||
* based on the selected {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public QueryString fromRegion(Class<?> domainType, Region<?, ?> region) {
|
||||
return QueryString.of(this.query.replaceAll(REGION_PATTERN, region.getFullPath()));
|
||||
public QueryString fromRegion(Region<?, ?> region, Class<?> domainType) {
|
||||
return QueryString.of(getQuery().replaceAll(REGION_PATTERN, region.getFullPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter indexes used in this query.
|
||||
*
|
||||
* @return the parameter indexes used in this query or an empty {@link Iterable} if none are used.
|
||||
* @see java.lang.Iterable
|
||||
/**
|
||||
* @deprecated use {@link #fromRegion(Region, Class)}.
|
||||
*/
|
||||
public Iterable<Integer> getInParameterIndexes() {
|
||||
|
||||
Pattern pattern = Pattern.compile(IN_PARAMETER_PATTERN);
|
||||
|
||||
Matcher matcher = pattern.matcher(this.query);
|
||||
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
indexes.add(Integer.parseInt(matcher.group()));
|
||||
}
|
||||
|
||||
return indexes;
|
||||
@Deprecated
|
||||
public QueryString fromRegion(Class<?> domainType, Region<?, ?> region) {
|
||||
return fromRegion(region, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +384,7 @@ public class QueryString {
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
*/
|
||||
public QueryString orderBy(Sort sort) {
|
||||
public @NonNull QueryString orderBy(@Nullable Sort sort) {
|
||||
|
||||
if (hasSort(sort)) {
|
||||
|
||||
@@ -273,13 +397,20 @@ public class QueryString {
|
||||
orderByClause.append(String.format("%1$s %2$s", order.getProperty(), order.getDirection()));
|
||||
}
|
||||
|
||||
return new QueryString(String.format("%1$s %2$s", asDistinct(this.query), orderByClause.toString()));
|
||||
return new QueryString(String.format("%1$s %2$s", asDistinct(getQuery()), orderByClause.toString()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean hasSort(Sort sort) {
|
||||
/**
|
||||
* Null-safe method to determine whether the {@link Sort} is valid (i.e. has been specified by the caller).
|
||||
*
|
||||
* @param sort {@link Sort} to evaluate.
|
||||
* @return a boolean value indicating whether the {@link Sort} is valid.
|
||||
* @see org.springframework.data.domain.Sort
|
||||
*/
|
||||
private boolean hasSort(@Nullable Sort sort) {
|
||||
return sort != null && sort.iterator().hasNext();
|
||||
}
|
||||
|
||||
@@ -289,7 +420,7 @@ public class QueryString {
|
||||
* @param hints array of {@link String Strings} containing query hints.
|
||||
* @return a new {@link QueryString} if hints are not null or empty, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withHints(@NonNull String... hints) {
|
||||
public @NonNull QueryString withHints(@NonNull String... hints) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(hints)) {
|
||||
|
||||
@@ -300,7 +431,7 @@ public class QueryString {
|
||||
builder.append(String.format("'%s'", hint));
|
||||
}
|
||||
|
||||
return QueryString.of(String.format(HINTS_OQL_TEMPLATE, builder.toString(), this.query));
|
||||
return QueryString.of(String.format(HINTS_OQL_TEMPLATE, builder.toString(), getQuery()));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -312,10 +443,10 @@ public class QueryString {
|
||||
* @param importExpression {@link String} containing the import clause.
|
||||
* @return a new {@link QueryString} if an import was declared, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withImport(@NonNull String importExpression) {
|
||||
public @NonNull QueryString withImport(@NonNull String importExpression) {
|
||||
|
||||
return StringUtils.hasText(importExpression)
|
||||
? QueryString.of(String.format(IMPORT_OQL_TEMPLATE, importExpression, this.query))
|
||||
? QueryString.of(String.format(IMPORT_OQL_TEMPLATE, importExpression, getQuery()))
|
||||
: this;
|
||||
}
|
||||
|
||||
@@ -325,10 +456,10 @@ public class QueryString {
|
||||
* @param limit {@link Integer} indicating the number of results to return from the query.
|
||||
* @return a new {@link QueryString} if a limit was specified, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withLimit(@NonNull Integer limit) {
|
||||
public @NonNull QueryString withLimit(@NonNull Integer limit) {
|
||||
|
||||
return limit != null
|
||||
? QueryString.of(String.format(LIMIT_OQL_TEMPLATE, this.query, limit))
|
||||
? QueryString.of(String.format(LIMIT_OQL_TEMPLATE, getQuery(), limit))
|
||||
: this;
|
||||
}
|
||||
|
||||
@@ -337,19 +468,21 @@ public class QueryString {
|
||||
*
|
||||
* @return a new {@link QueryString} with tracing enabled.
|
||||
*/
|
||||
public QueryString withTrace() {
|
||||
return QueryString.of(String.format(TRACE_OQL_TEMPLATE, this.query));
|
||||
public @NonNull QueryString withTrace() {
|
||||
return QueryString.of(String.format(TRACE_OQL_TEMPLATE, getQuery()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link String} representation of this {@link QueryString}.
|
||||
*
|
||||
* Returns the complete {@link String OQL query statement}.
|
||||
*
|
||||
* @return a {@link String} representation of this {@link QueryString}.
|
||||
* @see java.lang.Object#toString()
|
||||
* @see java.lang.String
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.query;
|
||||
return getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,29 @@ package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.repository.Query;
|
||||
import org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor;
|
||||
import org.springframework.data.gemfire.repository.query.support.PagingUtils;
|
||||
import org.springframework.data.gemfire.repository.query.support.TemplateBasedOqlQueryExecutor;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -39,6 +48,17 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.Query
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireRepositoryQuery
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
@@ -49,6 +69,9 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
private final GemfireTemplate template;
|
||||
|
||||
private final OqlQueryExecutor nonPagedQueryExecutor;
|
||||
private final OqlQueryExecutor pagedQueryExecutor;
|
||||
|
||||
private final QueryString query;
|
||||
|
||||
/**
|
||||
@@ -57,6 +80,8 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
StringBasedGemfireRepositoryQuery() {
|
||||
|
||||
this.query = null;
|
||||
this.nonPagedQueryExecutor = (queryMethod, query, arguments) -> null;
|
||||
this.pagedQueryExecutor = (queryMethod, query, arguments) -> null;
|
||||
this.template = null;
|
||||
|
||||
register(ProvidedQueryPostProcessors.LIMIT
|
||||
@@ -91,6 +116,12 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
this.query = QueryString.of(query);
|
||||
this.template = template;
|
||||
|
||||
this.nonPagedQueryExecutor = new TemplateBasedOqlQueryExecutor(template);
|
||||
|
||||
this.pagedQueryExecutor = new SmartPagedOqlQueryExecutor(template)
|
||||
.thenExecuteWith(new TwoPhasePagedOqlQueryExecutor(template)
|
||||
.thenExecuteWith(new TemplateBasedOqlQueryExecutor(template)));
|
||||
|
||||
register(ProvidedQueryPostProcessors.LIMIT
|
||||
.processBefore(ProvidedQueryPostProcessors.IMPORT)
|
||||
.processBefore(ProvidedQueryPostProcessors.HINT)
|
||||
@@ -150,6 +181,30 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
return this.userDefinedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link OqlQueryExecutor} (strategy) used to execute Apache Geode
|
||||
* {@link Page non-paged} {@link String OQL queries}.
|
||||
*
|
||||
* @return the configured {@link OqlQueryExecutor} (strategy) used to execute Apache Geode
|
||||
* {@link Page non-paged} {@link String OQL queries}.
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
*/
|
||||
protected @NonNull OqlQueryExecutor getNonPagedQueryExecutor() {
|
||||
return this.nonPagedQueryExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link OqlQueryExecutor} (strategy) used to execute Apache Geode
|
||||
* {@link Page paged} {@link String OQL queries}.
|
||||
*
|
||||
* @return the configured {@link OqlQueryExecutor} (strategy) used to execute Apache Geode
|
||||
* {@link Page paged} {@link String OQL queries}.
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
*/
|
||||
protected @NonNull OqlQueryExecutor getPagedQueryExecutor() {
|
||||
return this.pagedQueryExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link QueryString managed query}.
|
||||
*
|
||||
@@ -178,20 +233,42 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
QueryMethod queryMethod = getQueryMethod();
|
||||
|
||||
QueryString query = preProcess(queryMethod, getQuery(), arguments);
|
||||
QueryString query = getQuery();
|
||||
|
||||
String preparedQuery = prepareQuery(queryMethod, query, arguments);
|
||||
|
||||
SelectResults<?> selectResults =
|
||||
resolveOqlQueryExecutor(queryMethod).execute(queryMethod, preparedQuery, arguments);
|
||||
|
||||
return processQueryResults(queryMethod, selectResults, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the OQL query statement to execute.
|
||||
*
|
||||
* @param queryMethod {@link QueryMethod} modeling the OQL query.
|
||||
* @param query {@link QueryString} containing the OQL query statement.
|
||||
* @param arguments array of {@link Object} values containing the arguments for the OQL query bind in parameters.
|
||||
* @return the {@literal prepared} OQL query to execute.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see #bindInParameters(QueryMethod, QueryString, Object[])
|
||||
* @see #resolveFromClause(QueryMethod, QueryString)
|
||||
* @see #getQueryPostProcessor()
|
||||
*/
|
||||
protected @NonNull String prepareQuery(@NonNull QueryMethod queryMethod, @NonNull QueryString query,
|
||||
@NonNull Object[] arguments) {
|
||||
|
||||
query = bindInParameters(queryMethod, resolveFromClause(queryMethod, query), arguments);
|
||||
|
||||
String queryString = query.toString();
|
||||
String processedQueryString = getQueryPostProcessor().postProcess(queryMethod, queryString, arguments);
|
||||
|
||||
SelectResults<?> selectResults = getTemplate().find(processedQueryString, arguments);
|
||||
|
||||
return postProcess(queryMethod, selectResults);
|
||||
return processedQueryString;
|
||||
}
|
||||
|
||||
QueryString preProcess(QueryMethod queryMethod, QueryString query, Object[] arguments) {
|
||||
|
||||
query = isUserDefinedQuery() ? query
|
||||
: query.fromRegion(queryMethod.getEntityInformation().getJavaType(), getTemplate().getRegion());
|
||||
private QueryString bindInParameters(QueryMethod queryMethod, QueryString query, Object[] arguments) {
|
||||
|
||||
ParametersParameterAccessor parameterAccessor =
|
||||
new ParametersParameterAccessor(queryMethod.getParameters(), arguments);
|
||||
@@ -203,13 +280,53 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
return query;
|
||||
}
|
||||
|
||||
Object postProcess(QueryMethod queryMethod, SelectResults<?> selectResults) {
|
||||
private QueryString resolveFromClause(QueryMethod queryMethod, QueryString query) {
|
||||
|
||||
Collection<?> collection = toCollection(selectResults);
|
||||
return isUserDefinedQuery() ? query
|
||||
: query.fromRegion(getTemplate().getRegion(), queryMethod.getEntityInformation().getJavaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link OqlQueryExecutor} used to execute the {@link String OQL query statement} modeled by
|
||||
* the given {@link QueryMethod}.
|
||||
*
|
||||
* @param queryMethod {@link QueryMethod} used to resolve the {@link OqlQueryExecutor}; must not be {@literal null}.
|
||||
* @return the resolve {@link OqlQueryExecutor} appropriate for executing the {@link String OQL query statement}
|
||||
* modeled by the give {@link QueryMethod}.
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
protected @NonNull OqlQueryExecutor resolveOqlQueryExecutor(@NonNull QueryMethod queryMethod) {
|
||||
|
||||
return PagingUtils.isPagingPresent(queryMethod)
|
||||
? getPagedQueryExecutor()
|
||||
: getNonPagedQueryExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the OQL query {@link SelectResults result set}.
|
||||
*
|
||||
* @param queryMethod {@link QueryMethod} modeling the OQL query.
|
||||
* @param selectResults {@link SelectResults} from the execution of the OQL query.
|
||||
* @return the OQL query results.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query result does not match
|
||||
* the {@link QueryMethod} {@link Class return type}.
|
||||
* @throws IllegalStateException if the OQL query is not supported based on the return value.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected @Nullable Object processQueryResults(@NonNull QueryMethod queryMethod,
|
||||
@NonNull SelectResults<?> selectResults, @NonNull Object... arguments) {
|
||||
|
||||
Collection collection = toCollection(selectResults);
|
||||
|
||||
if (queryMethod.isCollectionQuery()) {
|
||||
return collection;
|
||||
}
|
||||
else if (queryMethod.isPageQuery()) {
|
||||
return new PageImpl<Object>(new ArrayList<>(collection), PagingUtils.getPageRequest(queryMethod, arguments), Integer.MAX_VALUE);
|
||||
}
|
||||
else if (queryMethod.isQueryForEntity()) {
|
||||
if (collection.isEmpty()) {
|
||||
return null;
|
||||
@@ -229,8 +346,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
boolean isSingleNonEntityResult(QueryMethod method, Collection<?> result) {
|
||||
private boolean isSingleNonEntityResult(QueryMethod method, Collection<?> result) {
|
||||
|
||||
Class<?> methodReturnType = method.getReturnedObjectType();
|
||||
|
||||
@@ -252,21 +368,14 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @see org.springframework.util.CollectionUtils#arrayToList(Object)
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
Collection<?> toCollection(Object source) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@NonNull Collection toCollection(@Nullable Object source) {
|
||||
|
||||
if (source instanceof SelectResults) {
|
||||
return ((SelectResults<?>) source).asList();
|
||||
}
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return (Collection<?>) source;
|
||||
}
|
||||
|
||||
if (source == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source);
|
||||
return source == null ? Collections.emptyList()
|
||||
: source instanceof SelectResults ? ((SelectResults) source).asList()
|
||||
: source instanceof Collection ? (Collection<?>) source
|
||||
: source.getClass().isArray() ? CollectionUtils.arrayToList(source)
|
||||
: Collections.singletonList(source);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@@ -344,4 +453,281 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link SimplePagedOqlQueryExecutor} implementation that implements a paged OQL query statement
|
||||
* using a 2-phase approach.
|
||||
*
|
||||
* The first phase executes a {@literal keys query} (or OQL query for keys) satisfying the user's defined
|
||||
* OQL query predicate(s) specified in the {@literal WHERE} clause, {@link Sort sorted} according to
|
||||
* the user-defined {@literal ORDER BY} clause.
|
||||
*
|
||||
* The returned keys are then filtered by the {@link Pageable requested page}.
|
||||
*
|
||||
* Then, in the second (and final) phase, the keys returned by the {@literal keys query} are used as a predicate
|
||||
* in the user's original OQL query statement to limit the results returned to exactly those keys satisfying
|
||||
* the {@link Pageable requested page}.
|
||||
*
|
||||
* @see SimplePagedOqlQueryExecutor
|
||||
*/
|
||||
static class TwoPhasePagedOqlQueryExecutor extends SimplePagedOqlQueryExecutor {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TwoPhasePagedOqlQueryExecutor} initialized with the given, required
|
||||
* {@link GemfireTemplate}.
|
||||
*
|
||||
* @param template {@link GemfireTemplate} used to execute Apache Geode OQL queries; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireTemplate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
TwoPhasePagedOqlQueryExecutor(@NonNull GemfireTemplate template) {
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply return the {@link SelectResults} as is.
|
||||
*
|
||||
* The {@link SelectResults} were already limited to the {@link Pageable requested page} in this 2-phased
|
||||
* paged query implementation.
|
||||
*
|
||||
* @param selectResults {@link SelectResults} to process; must not be {@literal null}.
|
||||
* @param pageRequest {@link Pageable} object encapsulating the details of the {@link Page requested page};
|
||||
* must not be {@literal null}.
|
||||
* @return the {@link SelectResults} as is.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected SelectResults processPagedQueryResults(SelectResults selectResults, Pageable pageRequest) {
|
||||
//return selectResults;
|
||||
return super.processPagedQueryResults(selectResults, pageRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@literal smart} {@link PageLimitingOqlQueryExecutor} implementation that looks ahead at
|
||||
* the {@link Pageable requested page}, and if the user requested page on or the number of results needed
|
||||
* to satisfy the contents of the page are withing a pre-defined, configurable/tunable threshold, then the limited
|
||||
* {@link SelectResults OQL query result set} is returned.
|
||||
*
|
||||
* Alternatively, a 2-phased paged query can be used when the page number or page size is relatively large. Another
|
||||
* consideration is the size of the objects returned in the {@link SelectResults OQL query result set}.
|
||||
*
|
||||
* @see PageLimitingOqlQueryExecutor
|
||||
*/
|
||||
class SmartPagedOqlQueryExecutor extends PageLimitingOqlQueryExecutor {
|
||||
|
||||
// TODO enable this parameter to be configurable/tunable using the Spring Environment
|
||||
final int PAGED_QUERY_RESULT_SET_LIMIT_THRESHOLD =
|
||||
Integer.getInteger("spring.data.gemfire.query.limit.threshold", 101);
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SmartPagedOqlQueryExecutor} initialized with the given, required
|
||||
* {@link GemfireTemplate}.
|
||||
*
|
||||
* @param template {@link GemfireTemplate} used to execute Apache Geode OQL queries; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireTemplate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
SmartPagedOqlQueryExecutor(@NonNull GemfireTemplate template) {
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected SelectResults doExecute(Pageable pageRequest, QueryMethod queryMethod, String query,
|
||||
Object... arguments) {
|
||||
|
||||
if (isExecutable(pageRequest)) {
|
||||
return super.doExecute(pageRequest, queryMethod, query, arguments);
|
||||
}
|
||||
|
||||
throw newUnsupportedQueryExecutionException(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this {@link OqlQueryExecutor} can execute the {@link String OQL query} directly (as is).
|
||||
*
|
||||
* Just execute the OQL query as is, bypassing the 2-phase implementation, if the requested page
|
||||
* is the first page or the query result set size would be less than the threshold (e.g. 101 results).
|
||||
* Dynamically supports 10 pages if page size were 10, 5 pages if page size were 20, 4 pages if page size
|
||||
* were 25, 2 pages if page size were 50 and so on.
|
||||
*
|
||||
* @param pageRequest {@link Pageable} object encapsulating the details of the requested page.
|
||||
* @return a boolean value indicating whether the original {@link String OQL query} should be executed directly,
|
||||
* bypassing the 2-phase implementation.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
protected boolean isExecutable(@NonNull Pageable pageRequest) {
|
||||
|
||||
return PagingUtils.isPageOne(pageRequest)
|
||||
|| PagingUtils.getQueryResultSetLimitForPage(pageRequest) < PAGED_QUERY_RESULT_SET_LIMIT_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link SimplePagedOqlQueryExecutor} implementation that applies a {@literal LIMIT} to
|
||||
* the {@link String OQL query statement} based on the {@link Pageable requested page} in order to limit
|
||||
* the {@link SelectResults query result set}, or number of {@link Object results}, returned by
|
||||
* the {@link String OQL query}.
|
||||
*
|
||||
* @see SimplePagedOqlQueryExecutor
|
||||
*/
|
||||
class PageLimitingOqlQueryExecutor extends SimplePagedOqlQueryExecutor {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link PageLimitingOqlQueryExecutor} initialized with the given, required
|
||||
* {@link GemfireTemplate}.
|
||||
*
|
||||
* @param template {@link GemfireTemplate} used to execute Apache Geode OQL queries; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireTemplate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
PageLimitingOqlQueryExecutor(@NonNull GemfireTemplate template) {
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected String preparePagedQuery(String query, Pageable pageRequest) {
|
||||
|
||||
String pagedQuery = super.preparePagedQuery(query, pageRequest);
|
||||
|
||||
QueryString pagedQueryString = QueryString.of(pagedQuery);
|
||||
|
||||
int pagedQueryResultSetLimit = PagingUtils.getQueryResultSetLimitForPage(pageRequest);
|
||||
|
||||
if (pagedQueryString.isLimited()) {
|
||||
|
||||
int queryLimit = pagedQueryString.getLimit();
|
||||
|
||||
if (pagedQueryResultSetLimit < queryLimit) {
|
||||
pagedQueryString = pagedQueryString.adjustLimit(pagedQueryResultSetLimit);
|
||||
}
|
||||
else {
|
||||
|
||||
int startIndex = PagingUtils.getQueryResultSetStartIndexForPage(pageRequest);
|
||||
|
||||
Assert.state(queryLimit > startIndex,
|
||||
() -> String.format("The user-defined OQL query result set LIMIT [%d] must be greater than the requested page offset [%d]",
|
||||
queryLimit, startIndex));
|
||||
|
||||
int endIndex = PagingUtils.getQueryResultSetEndIndexForPage(pageRequest);
|
||||
|
||||
if (queryLimit < endIndex) {
|
||||
getLogger().warn(String.format("The requested page ending at index [%d] may be truncated by the user-defined OQL query result set LIMIT [%d]",
|
||||
endIndex, queryLimit));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
pagedQueryString = pagedQueryString.withLimit(pagedQueryResultSetLimit);
|
||||
}
|
||||
|
||||
return pagedQueryString.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link Page paged} OQL queries and {@link OqlQueryExecutor} implementations.
|
||||
*
|
||||
* This base class implementation simply returns the entire/full (i.e. non-limited) {@link SelectResults OQL query result set}
|
||||
* and then performs the paging logic to extract subsets of the results based on the {@link Page requested page}.
|
||||
*
|
||||
* @see org.springframework.data.gemfire.repository.query.support.TemplateBasedOqlQueryExecutor
|
||||
*/
|
||||
static abstract class SimplePagedOqlQueryExecutor extends TemplateBasedOqlQueryExecutor {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SimplePagedOqlQueryExecutor} initialized with the given, required
|
||||
* {@link GemfireTemplate}.
|
||||
*
|
||||
* @param template {@link GemfireTemplate} used to execute Apache Geode OQL queries; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireTemplate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
SimplePagedOqlQueryExecutor(@NonNull GemfireTemplate template) {
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public @NonNull SelectResults execute(@NonNull QueryMethod queryMethod, @NonNull String query,
|
||||
@NonNull Object... arguments) {
|
||||
|
||||
if (PagingUtils.isPagingPresent(queryMethod)) {
|
||||
|
||||
Pageable pageRequest = PagingUtils.getPageRequest(queryMethod, arguments);
|
||||
|
||||
return doExecute(pageRequest, queryMethod, query, arguments);
|
||||
}
|
||||
|
||||
throw newUnsupportedQueryExecutionException(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the {@link String OQL query statement}.
|
||||
*
|
||||
* @param pageRequest {@link Pageable} object encapsulating the details of the {@link Page requested paged};
|
||||
* must not be {@literal null}.
|
||||
* @param queryMethod {@link QueryMethod} modeling the {@link String OQL query statement} to be executed;
|
||||
* must not be {@literal null}.
|
||||
* @param query {@link String} containing the OQL query statement;
|
||||
* must not be {@literal null} or {@literal empty}.
|
||||
* @param arguments array of {@link Object arguments} passed to the placeholders in
|
||||
* the {@link String OQL query statement}.
|
||||
* @return the {@link SelectResults} from executing the {@link String OQL query statement}.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected SelectResults doExecute(@NonNull Pageable pageRequest, @NonNull QueryMethod queryMethod,
|
||||
@NonNull String query, @NonNull Object... arguments) {
|
||||
|
||||
String preparedQuery = preparePagedQuery(query, pageRequest);
|
||||
|
||||
SelectResults selectResults = super.execute(queryMethod, preparedQuery, arguments);
|
||||
|
||||
return processPagedQueryResults(selectResults, pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the required {@link String OQL query statement} as a paged query.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query statement to prepare.
|
||||
* @param pageRequest {@link Pageable} object containing the details of the {@link Page requested page}.
|
||||
* @return the prepared {@link String OQL query}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
protected @NonNull String preparePagedQuery(String query, Pageable pageRequest) {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the {@link SelectResults} as a {@link Page paged} query result set.
|
||||
*
|
||||
* @param selectResults {@link SelectResults} to process; must not be {@literal null}.
|
||||
* @param pageRequest {@link Pageable} object encapsulating the details of the {@link Page requested page};
|
||||
* must not be {@literal null}.
|
||||
* @return the processed {@link SelectResults}.
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected @NonNull SelectResults processPagedQueryResults(@NonNull SelectResults selectResults,
|
||||
@NonNull Pageable pageRequest) {
|
||||
|
||||
return new PagedSelectResults(selectResults, pageRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query.support;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The OqlKeyword enum represents the range of keywords (Reserved Words)
|
||||
* in GemFire's Object Query Language (OQL).
|
||||
* The {@link OqlKeyword} enum represents the complete set of keywords (Reserved Words)
|
||||
* in Apache Geode's Object Query Language (OQL).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see <a href="https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_additional/supported_keywords.html">Supported Keywords</a>
|
||||
* @see <a href="https://geode.apache.org/docs/guide/113/developing/query_additional/supported_keywords.html">Supported Keywords</a>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum OqlKeyword {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A Strategy interface for executing Apache Geode OQL queries (e.g. {@literal SELECT} statements).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OqlQueryExecutor {
|
||||
|
||||
String NON_EXECUTABLE_QUERY_MESSAGE = "OQL query [%1$s] is not executable by this executor [%2$s]";
|
||||
|
||||
/**
|
||||
* Executes the given {@link String OQL query}.
|
||||
*
|
||||
* @param queryMethod {@link QueryMethod} modeling the OQl query.
|
||||
* @param query {@link String} containing the Apache Geode OQL query.
|
||||
* @param arguments array of {@link Object arguments} used for the bind in OQL query parameters.
|
||||
* @return the {@link SelectResults OQL query result set}.
|
||||
* @throws UnsupportedQueryExecutionException if this {@link OqlQueryExecutor} cannot execute (i.e. handle)
|
||||
* the OQL query.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
SelectResults execute(QueryMethod queryMethod, String query, Object... arguments);
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnsupportedQueryExecutionException} initialized with a canned message
|
||||
* containing the given OQL query that could not be executed by the {@link OqlQueryExecutor} implementation.
|
||||
*
|
||||
* @param query {@link String OQL query} that could not be executed.
|
||||
* @return a new {@link UnsupportedQueryExecutionException}.
|
||||
* @see org.springframework.data.gemfire.repository.query.support.UnsupportedQueryExecutionException
|
||||
*/
|
||||
default UnsupportedQueryExecutionException newUnsupportedQueryExecutionException(String query) {
|
||||
return new UnsupportedQueryExecutionException(String.format(NON_EXECUTABLE_QUERY_MESSAGE, query,
|
||||
this.getClass().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe composition method to {@literal compose} {@literal this} {@link OqlQueryExecutor} with
|
||||
* the given {@link OqlQueryExecutor}.
|
||||
*
|
||||
* {@link OqlQueryExecutor} implementations should be {@literal composed} in an order that is most suitable to
|
||||
* the execution of the OQL query first. Meaning, the outer most {@link OqlQueryExecutor} should be the most
|
||||
* suitable {@link OqlQueryExecutor} to execute the given OQL query followed by the next most suitable
|
||||
* {@link OqlQueryExecutor} in the composition (i.e. chain) and so on until the OQL query is either successfully
|
||||
* executed (handled) or the composition is exhausted, in which case, an {@link Exception} could be thrown.
|
||||
*
|
||||
* If an {@link OqlQueryExecutor is unable to execute, or handle, the given OQL query, then it must throw
|
||||
* an {@link UnsupportedQueryExecutionException } to triggger the next {@link OqlQueryExecutor} in the composition.
|
||||
*
|
||||
* @param queryExecutor {@link OqlQueryExecutor} to compose with this {@link OqlQueryExecutor};
|
||||
* must not be {@literal null}.
|
||||
* @return a composed {@link OqlQueryExecutor} consisting of this {@link OqlQueryExecutor} composed with
|
||||
* the given {@link OqlQueryExecutor}. If the {@link OqlQueryExecutor} is {@literal null}, then this method
|
||||
* returns this {@link OqlQueryExecutor}.
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Composite_pattern">Composite Software Design Pattern</a>
|
||||
*/
|
||||
default OqlQueryExecutor thenExecuteWith(@NonNull OqlQueryExecutor queryExecutor) {
|
||||
|
||||
return queryExecutor == null ? this
|
||||
: (queryMethod, query, arguments) -> {
|
||||
try {
|
||||
return this.execute(queryMethod, query, arguments);
|
||||
}
|
||||
catch (UnsupportedQueryExecutionException cause) {
|
||||
return queryExecutor.execute(queryMethod, query, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class used to work with {@link Collection}, {@link Page} and {@link Pageable} objects.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Collection
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.repository.support.PageableExecutionUtils
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public abstract class PagingUtils {
|
||||
|
||||
public static final String INVALID_PAGE_NUMBER_MESSAGE = "Page Number [%d] must be greater than equal to 0";
|
||||
public static final String INVALID_PAGE_SIZE_MESSAGE = "Page Size [%d] must be greater than equal to 1";
|
||||
public static final String NON_NULL_PAGEABLE_MESSAGE = "Pageable must not be null";
|
||||
|
||||
protected static final Function<QueryMethod, Boolean> DEFAULT_IS_PAGE_QUERY_FUNCTION = QueryMethod::isPageQuery;
|
||||
|
||||
static Function<QueryMethod, Boolean> isPageQueryFunction = DEFAULT_IS_PAGE_QUERY_FUNCTION;
|
||||
|
||||
static Function<QueryMethod, Boolean> hasPageableParameterFunction = queryMethod ->
|
||||
Optional.ofNullable(queryMethod)
|
||||
.map(QueryMethod::getParameters)
|
||||
.filter(Parameters::hasPageableParameter)
|
||||
.isPresent();
|
||||
|
||||
/**
|
||||
* Asserts that the {@link Pageable} object is valid.
|
||||
*
|
||||
* @param pageable {@link Pageable} object to evaluate.
|
||||
* @throws IllegalArgumentException if {@link Pageable} is {@literal null} or page number is less than {@literal 0}
|
||||
* or the page size is less than {@literal 1}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
public static void assertPageable(@NonNull Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, NON_NULL_PAGEABLE_MESSAGE);
|
||||
|
||||
int pageNumber = pageable.getPageNumber();
|
||||
|
||||
Assert.isTrue(pageNumber >= 0, () -> String.format(INVALID_PAGE_NUMBER_MESSAGE, pageNumber));
|
||||
|
||||
int pageSize = pageable.getPageSize();
|
||||
|
||||
Assert.isTrue(pageSize > 0, () -> String.format(INVALID_PAGE_SIZE_MESSAGE, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link Pageable page request} is for page one.
|
||||
*
|
||||
* @param pageable {@link Pageable page request} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link Pageable page request} is for page one.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
public static boolean isPageOne(@NonNull Pageable pageable) {
|
||||
return pageable != null && pageable.getPageNumber() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to determine whether the given {@link QueryMethod} represents (models) a paged query.
|
||||
*
|
||||
* @param queryMethod {@link QueryMethod} to evaluate for paging.
|
||||
* @return a boolean value indicating whether the given {@link QueryMethod} represents (models) a paged query.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
public static boolean isPagingPresent(@Nullable QueryMethod queryMethod) {
|
||||
|
||||
return queryMethod != null
|
||||
&& (isPageQueryFunction.apply(queryMethod)
|
||||
|| hasPageableParameterFunction.apply(queryMethod));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@literal page} from the given {@link List}.
|
||||
*
|
||||
* This method is {@literal null-safe}, and guards against a {@literal null} {@link List} and {@link Pageable}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link List} elements; must not be {@literal null}.
|
||||
* @param list {@link List} from which to extract a page of elements; must not be {@literal null}.
|
||||
* @param pageable {@link Pageable} object encapsulating the details for the page requested.
|
||||
* @return a {@link List sub-List} containing the contents for the requested page.
|
||||
* @see #getQueryResultSetStartIndexForPage(Pageable)
|
||||
* @see #getQueryResultSetEndIndexForPage(Pageable)
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see java.util.List
|
||||
*/
|
||||
public static @NonNull <T> List<T> getPagedList(@NonNull List<T> list, @NonNull Pageable pageable) {
|
||||
|
||||
list = CollectionUtils.nullSafeList(list);
|
||||
|
||||
int total = list.size();
|
||||
int startIndex = getQueryResultSetStartIndexForPage(pageable);
|
||||
int endIndex = getQueryResultSetEndIndexForPage(pageable);
|
||||
|
||||
return list.isEmpty() || total <= startIndex
|
||||
? Collections.emptyList()
|
||||
: list.subList(startIndex, Math.min(total, endIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@link Pageable page request} argument from an array of arguments passed to
|
||||
* the given {@link QueryMethod}.
|
||||
*
|
||||
* @param queryMethod invoked {@link QueryMethod}; must not be {@literal null}.
|
||||
* @param arguments array of {@link Object arguments} passed to the {@link QueryMethod};
|
||||
* must not be {@literal null}.
|
||||
* @return the {@link Pageable page request} argument in the array of {@link Object arguments}
|
||||
* passed to the {@link QueryMethod}.
|
||||
* @throws IllegalArgumentException if {@link QueryMethod} is {@literal null}, or the {@link QueryMethod} parameter
|
||||
* count is not equal to the argument count, or the indexed {@link QueryMethod} argument is not an instance of
|
||||
* {@link Pageable}.
|
||||
* @throws IllegalStateException if the {@link QueryMethod} does not have a {@link Pageable} parameter.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
public static @NonNull Pageable getPageRequest(@NonNull QueryMethod queryMethod, @NonNull Object... arguments) {
|
||||
|
||||
Assert.notNull(queryMethod, "QueryMethod must not be null");
|
||||
|
||||
Parameters<?, ?> queryMethodParameters = queryMethod.getParameters();
|
||||
|
||||
Assert.state(queryMethodParameters.hasPageableParameter(),
|
||||
() -> String.format("QueryMethod [%s] does not have a Pageable parameter", queryMethod));
|
||||
|
||||
arguments = ArrayUtils.nullSafeArray(arguments, Object.class);
|
||||
|
||||
long queryMethodArgumentCount = arguments.length;
|
||||
long queryMethodParameterCount = queryMethodParameters.stream().count();
|
||||
|
||||
Assert.isTrue(queryMethodArgumentCount == queryMethodParameterCount,
|
||||
() -> String.format("The number of arguments [%d] must match the number of QueryMethod [%s] parameters [%d]",
|
||||
queryMethodArgumentCount, queryMethod, queryMethodParameterCount));
|
||||
|
||||
int pageableIndex = queryMethodParameters.getPageableIndex();
|
||||
|
||||
Object pageableArgument = arguments[pageableIndex];
|
||||
|
||||
Assert.isInstanceOf(Pageable.class, pageableArgument,
|
||||
() -> String.format("Argument [%1$s] must be of type [%2$s]", pageableArgument, Pageable.class.getName()));
|
||||
|
||||
return (Pageable) pageableArgument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to determine the starting index in the query result set for populating the content
|
||||
* of the {@link Page}.
|
||||
*
|
||||
* @param pageable {@link Pageable} object encapsulating the details of the requested {@link Page}.
|
||||
* @return the start index in the query result set to populate the content of the {@link Page}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see #getQueryResultSetEndIndexForPage(Pageable)
|
||||
*/
|
||||
public static int getQueryResultSetStartIndexForPage(@Nullable Pageable pageable) {
|
||||
return pageable != null ? pageable.getPageNumber() * pageable.getPageSize() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to determine the end index in the query result set for populating the content
|
||||
* of the {@link Page}.
|
||||
*
|
||||
* @param pageable {@link Pageable} object encapsulating the details of the requested {@link Page}.
|
||||
* @return the end index in the query result set to populate the content of the {@link Page}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see #getQueryResultSetStartIndexForPage(Pageable)
|
||||
*/
|
||||
public static int getQueryResultSetEndIndexForPage(@Nullable Pageable pageable) {
|
||||
return pageable != null ? getQueryResultSetStartIndexForPage(pageable) + pageable.getPageSize() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to determine the maximum results that would be returned by a query
|
||||
* given the {@link Pageable} object specifying the requested {@link Page}.
|
||||
*
|
||||
* @param pageable {@link Pageable} object encapsulating the details of the requested {@link Page}.
|
||||
* @return the maximum results that would be returned by a query given the {@link Pageable} object
|
||||
* specifying the requested {@link Page}.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see #normalizePageNumber(Pageable)
|
||||
*/
|
||||
public static int getQueryResultSetLimitForPage(@Nullable Pageable pageable) {
|
||||
return pageable != null ? normalizePageNumber(pageable) * pageable.getPageSize() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to normalize 0 index based page numbers (i.e. 0, 1, 2, ...) to natural page numbers
|
||||
* (i.e. 1, 2, 3, ...) using the given {@link Page}.
|
||||
*
|
||||
* @param page {@link Page} used to determine the page number to normalize.
|
||||
* @return the normalized page number from the 0 index based page number.
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see #normalize(int)
|
||||
*/
|
||||
public static int normalizePageNumber(@Nullable Page<?> page) {
|
||||
return page != null ? normalize(page.getNumber()) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to normalize 0 index based page numbers (i.e. 0, 1, 2, ...) to natural page numbers
|
||||
* (i.e. 1, 2, 3, ...) using the given {@link Pageable}.
|
||||
*
|
||||
* @param pageable {@link Pageable} used to determine the page number to normalize.
|
||||
* @return the normalized page number from the 0 index based page number.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see #normalize(int)
|
||||
*/
|
||||
public static int normalizePageNumber(@Nullable Pageable pageable) {
|
||||
return pageable != null ? normalize(pageable.getPageNumber()) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes 0 index based page numbers (i.e. 0, 1, 2, ...) to natural page numbers (i.e. 1, 2, 3, ...).
|
||||
*
|
||||
* @param pageNumber The {@link Integer#TYPE page number} to normalize.
|
||||
* @return the normalized page number from the 0 index based page number.
|
||||
*/
|
||||
protected static int normalize(int pageNumber) {
|
||||
return Math.max(pageNumber, -1) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine the size (number of elements) of the {@link Iterable}.
|
||||
*
|
||||
* The {@link Iterable} object may be an array, a {@link Collection} or simply a stream backing,
|
||||
* pure {@link Iterable} object.
|
||||
*
|
||||
* @param iterable {@link Iterable} object to evaluate.
|
||||
* @return the size (number of elements) contained by the {@link Iterable} object. If the {@link Iterable} object
|
||||
* is {@literal null}, then this method will return {@literal 0}.
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
protected static long nullSafeSize(@Nullable Iterable<?> iterable) {
|
||||
|
||||
return iterable == null ? 0
|
||||
: iterable instanceof Collection ? ((Collection<?>) iterable).size()
|
||||
: StreamSupport.stream(iterable.spliterator(), false).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link Page} view from the given {@link List} based on the {@link Pageable} object (page request).
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link List} elements.
|
||||
* @param list {@link List} of content from which to extract a {@link Page}; must not be {@literal null}.
|
||||
* @param pageable {@link Pageable} object encapsulating the details of the {@link Page} requested;
|
||||
* must not be {@literal null}.
|
||||
* @return a {@literal non-null} {@link Page} view from the given {@link List} based on the {@link Pageable} object
|
||||
* (page request).
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see #getPagedList(List, Pageable)
|
||||
* @see java.util.List
|
||||
*/
|
||||
public static @NonNull <T> Page<T> toPage(@NonNull List<T> list, @NonNull Pageable pageable) {
|
||||
|
||||
List<T> pagedList = getPagedList(list, pageable);
|
||||
|
||||
return pagedList.isEmpty()
|
||||
? Page.empty()
|
||||
: new PageImpl<>(pagedList, pageable, nullSafeSize(list));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link OqlQueryExecutor} implementation using SDG's {@link GemfireTemplate} to execute Apache Geode
|
||||
* {@link String OQL queries}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class TemplateBasedOqlQueryExecutor implements OqlQueryExecutor {
|
||||
|
||||
private final GemfireTemplate template;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TemplateBasedOqlQueryExecutor} initialized with the given,
|
||||
* required {@link GemfireTemplate} used to execute Apache Geode {@link String OQL queries}.
|
||||
*
|
||||
* @param template {@link GemfireTemplate} used to execute Apache Geode {@link String OQL queries};
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireTemplate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
public TemplateBasedOqlQueryExecutor(@NonNull GemfireTemplate template) {
|
||||
|
||||
Assert.notNull(template, "GemfireTemplate must not be null");
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link GemfireTemplate} used to execute Apache Geode {@link String OQL queries}.
|
||||
*
|
||||
* @return the configured {@link GemfireTemplate} used to execute Apache Geode {@link String OQL queries}.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
protected @NonNull GemfireTemplate getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public SelectResults execute(QueryMethod queryMethod, @NonNull String query, @NonNull Object... arguments) {
|
||||
return getTemplate().find(query, arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
/**
|
||||
* A Java {@link RuntimeException} indicating that the Apache Geode OQL query could not be executed (i.e. handled)
|
||||
* by the {@link OqlQueryExecutor}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class UnsupportedQueryExecutionException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new, uninitialized instance of {@link UnsupportedQueryExecutionException}.
|
||||
*/
|
||||
public UnsupportedQueryExecutionException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnsupportedQueryExecutionException} initialized with
|
||||
* the given {@link String message} describing the exception.
|
||||
*
|
||||
* @param message {@link String} containing a description of the exception.
|
||||
* @see java.lang.String
|
||||
*/
|
||||
public UnsupportedQueryExecutionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnsupportedQueryExecutionException} initialized with
|
||||
* the given {@link Throwable} as the underlying {@literal cause} of this exception.
|
||||
*
|
||||
* @param cause {@link Throwable} used as the cause of this exception.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public UnsupportedQueryExecutionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnsupportedQueryExecutionException} initialized with
|
||||
* the given {@link String message} describing the exception and given {@link Throwable}
|
||||
* as the underlying {@literal cause} of this exception.
|
||||
*
|
||||
* @param message {@link String} containing a description of the exception.
|
||||
* @param cause {@link Throwable} used as the cause of this exception.
|
||||
* @see java.lang.String
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public UnsupportedQueryExecutionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireCallback;
|
||||
@@ -40,6 +39,7 @@ import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.repository.GemfireRepository;
|
||||
import org.springframework.data.gemfire.repository.Wrapper;
|
||||
import org.springframework.data.gemfire.repository.query.QueryString;
|
||||
import org.springframework.data.gemfire.repository.query.support.PagingUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.gemfire.util.SpringUtils;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
@@ -248,7 +248,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
public @NonNull Iterable<T> findAll(@NonNull Sort sort) {
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /RegionPlaceholder")
|
||||
.fromRegion(getEntityInformation().getJavaType(), getRegion())
|
||||
.fromRegion(getRegion(), getEntityInformation().getJavaType())
|
||||
.orderBy(sort);
|
||||
|
||||
SelectResults<T> selectResults = getTemplate().find(query.toString());
|
||||
@@ -356,25 +356,10 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
|
||||
@NonNull Page<T> toPage(@Nullable Iterable<T> iterable, @NonNull Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null");
|
||||
|
||||
int pageNumber = pageable.getPageNumber();
|
||||
int pageSize = pageable.getPageSize();
|
||||
|
||||
Assert.isTrue(pageNumber >= 0, () -> String.format("Page Number [%d] must be greater than equal to 0",
|
||||
pageNumber));
|
||||
|
||||
Assert.isTrue(pageSize > 0, () -> String.format("Page Size [%d] must be greater than equal to 1",
|
||||
pageSize));
|
||||
|
||||
int startIndex = pageNumber * pageSize;
|
||||
int endIndex = startIndex + pageSize;
|
||||
PagingUtils.assertPageable(pageable);
|
||||
|
||||
List<T> results = toList(iterable);
|
||||
|
||||
int total = results.size();
|
||||
|
||||
return results.isEmpty() || total <= startIndex ? Page.empty()
|
||||
: new PageImpl<>(results.subList(startIndex, Math.min(total, endIndex)), pageable, total);
|
||||
return PagingUtils.toPage(results, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ package org.springframework.data.gemfire.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -203,4 +206,54 @@ public abstract class ArrayUtils {
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given array into an {@link Iterable} object.
|
||||
*
|
||||
* @param <T> {@link Class type} of the array elements; mut not be {@literal null}.
|
||||
* @param array array to convert to an {@link Iterable}.
|
||||
* @return an {@link Iterable} object from the given array.
|
||||
* @throws IllegalArgumentException if the array is {@literal null}.
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Iterable<T> toIterable(@NonNull T... array) {
|
||||
return IterableArray.of(array);
|
||||
}
|
||||
|
||||
protected static class IterableArray<T> implements Iterable<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T> IterableArray<T> of(@NonNull T... array) {
|
||||
return new IterableArray<>(array);
|
||||
}
|
||||
|
||||
private final T[] array;
|
||||
|
||||
protected IterableArray(@NonNull T[] array) {
|
||||
|
||||
Assert.notNull(array, "Array must not be null");
|
||||
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
|
||||
return new Iterator<T>() {
|
||||
|
||||
int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.index < IterableArray.this.array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return IterableArray.this.array[this.index++];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -26,18 +25,19 @@ import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -45,26 +45,31 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.gemfire.repository.sample.User;
|
||||
import org.springframework.data.gemfire.util.CacheUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link GemfireTemplate}.
|
||||
* Integration Tests for {@link GemfireTemplate}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Properties
|
||||
* @see org.junit.Test
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class GemfireTemplateIntegrationTests {
|
||||
|
||||
protected static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
|
||||
protected static final String DEFAULT_GEMFIRE_LOG_LEVEL = "error";
|
||||
|
||||
protected static final List<User> TEST_USERS = new ArrayList<User>(9);
|
||||
protected static final List<User> TEST_USERS = new ArrayList<>(9);
|
||||
|
||||
static {
|
||||
TEST_USERS.add(newUser("jonDoe"));
|
||||
@@ -96,18 +101,22 @@ public class GemfireTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
protected static User newUser(String username, String email, Calendar since, Boolean active) {
|
||||
|
||||
User user = new User(username);
|
||||
|
||||
user.setActive(Boolean.TRUE.equals(active));
|
||||
user.setEmail(email);
|
||||
user.setSince(since);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
protected String getKey(User user) {
|
||||
return (user != null ? user.getUsername() : null);
|
||||
return user != null ? user.getUsername() : null;
|
||||
}
|
||||
|
||||
protected User getUser(String username) {
|
||||
|
||||
for (User user : TEST_USERS) {
|
||||
if (user.getUsername().equals(username)) {
|
||||
return user;
|
||||
@@ -118,8 +127,9 @@ public class GemfireTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
protected List<User> getUsers(String... usernames) {
|
||||
|
||||
List<String> usernameList = Arrays.asList(usernames);
|
||||
List<User> users = new ArrayList<User>(usernames.length);
|
||||
List<User> users = new ArrayList<>(usernames.length);
|
||||
|
||||
for (User user : TEST_USERS) {
|
||||
if (usernameList.contains(user.getUsername())) {
|
||||
@@ -139,7 +149,8 @@ public class GemfireTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
protected Map<String, User> getUsersAsMap(Iterable<User> users) {
|
||||
Map<String, User> userMap = new HashMap<String, User>();
|
||||
|
||||
Map<String, User> userMap = new HashMap<>();
|
||||
|
||||
for (User user : users) {
|
||||
userMap.put(getKey(user), user);
|
||||
@@ -149,133 +160,147 @@ public class GemfireTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
protected void assertNullEquals(Object value1, Object value2) {
|
||||
assertThat(value1 == null ? value2 == null : value1.equals(value2)).isTrue();
|
||||
assertThat(Objects.equals(value1, value2)).isTrue();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
assertThat(users).isNotNull();
|
||||
|
||||
if (users.isEmpty()) {
|
||||
assertThat(this.users).isNotNull();
|
||||
|
||||
if (this.users.isEmpty()) {
|
||||
for (User user : TEST_USERS) {
|
||||
users.put(getKey(user), user);
|
||||
this.users.put(getKey(user), user);
|
||||
}
|
||||
|
||||
assertThat(users.isEmpty()).isFalse();
|
||||
assertThat(users.size()).isEqualTo(TEST_USERS.size());
|
||||
assertThat(this.users.size()).isEqualTo(TEST_USERS.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsKey() {
|
||||
assertThat(usersTemplate.containsKey(getKey(getUser("jonDoe")))).isTrue();
|
||||
assertThat(usersTemplate.containsKey("dukeNukem")).isFalse();
|
||||
|
||||
assertThat(this.usersTemplate.containsKey(getKey(getUser("jonDoe")))).isTrue();
|
||||
assertThat(this.usersTemplate.containsKey("dukeNukem")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsKeyOnServer() {
|
||||
assumeThat(CacheUtils.isClient(gemfireCache), is(true));
|
||||
assertThat(usersTemplate.containsKeyOnServer(getKey(getUser("jackHandy")))).isTrue();
|
||||
assertThat(usersTemplate.containsKeyOnServer("maxPayne")).isFalse();
|
||||
|
||||
assumeThat(CacheUtils.isClient(this.gemfireCache), is(true));
|
||||
|
||||
assertThat(this.usersTemplate.containsKeyOnServer(getKey(getUser("jackHandy")))).isTrue();
|
||||
assertThat(this.usersTemplate.containsKeyOnServer("maxPayne")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsValue() {
|
||||
assertThat(usersTemplate.containsValue(getUser("pieDoe"))).isTrue();
|
||||
assertThat(usersTemplate.containsValue(newUser("pieDough"))).isFalse();
|
||||
|
||||
assertThat(this.usersTemplate.containsValue(getUser("pieDoe"))).isTrue();
|
||||
assertThat(this.usersTemplate.containsValue(newUser("pieDough"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsValueForKey() {
|
||||
assertThat(usersTemplate.containsValueForKey(getKey(getUser("cookieDoe")))).isTrue();
|
||||
assertThat(usersTemplate.containsValueForKey("chocolateChipCookieDoe")).isFalse();
|
||||
|
||||
assertThat(this.usersTemplate.containsValueForKey(getKey(getUser("cookieDoe")))).isTrue();
|
||||
assertThat(this.usersTemplate.containsValueForKey("chocolateChipCookieDoe")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create() {
|
||||
|
||||
User bartSimpson = newUser("bartSimpson");
|
||||
|
||||
usersTemplate.create(getKey(bartSimpson), bartSimpson);
|
||||
this.usersTemplate.create(getKey(bartSimpson), bartSimpson);
|
||||
|
||||
assertThat(users.containsKey(getKey(bartSimpson))).isTrue();
|
||||
assertThat(users.containsValueForKey(getKey(bartSimpson))).isTrue();
|
||||
assertThat(users.containsValue(bartSimpson)).isTrue();
|
||||
assertThat(users.get(getKey(bartSimpson))).isEqualTo(bartSimpson);
|
||||
assertThat(this.users.containsKey(getKey(bartSimpson))).isTrue();
|
||||
assertThat(this.users.containsValueForKey(getKey(bartSimpson))).isTrue();
|
||||
assertThat(this.users.containsValue(bartSimpson)).isTrue();
|
||||
assertThat(this.users.get(getKey(bartSimpson))).isEqualTo(bartSimpson);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
|
||||
String key = getKey(getUser("imaPigg"));
|
||||
|
||||
assertThat(usersTemplate.<Object, Object>get(key)).isEqualTo(users.get(key));
|
||||
assertNullEquals(users.get("mrT"), usersTemplate.get("mrT"));
|
||||
assertThat(this.usersTemplate.<Object, Object>get(key)).isEqualTo(this.users.get(key));
|
||||
assertNullEquals(this.users.get("mrT"), this.usersTemplate.get("mrT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put() {
|
||||
|
||||
User peterGriffon = newUser("peterGriffon");
|
||||
|
||||
assertThat(usersTemplate.put(getKey(peterGriffon), peterGriffon)).isNull();
|
||||
assertThat(users.get(getKey(peterGriffon))).isEqualTo(peterGriffon);
|
||||
assertThat(this.usersTemplate.put(getKey(peterGriffon), peterGriffon)).isNull();
|
||||
assertThat(this.users.get(getKey(peterGriffon))).isEqualTo(peterGriffon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putIfAbsent() {
|
||||
|
||||
User stewieGriffon = newUser("stewieGriffon");
|
||||
|
||||
assertThat(users.containsValue(stewieGriffon)).isFalse();
|
||||
assertThat(usersTemplate.putIfAbsent(getKey(stewieGriffon), stewieGriffon)).isNull();
|
||||
assertThat(users.containsValue(stewieGriffon)).isTrue();
|
||||
assertThat(usersTemplate.putIfAbsent(getKey(stewieGriffon), newUser("megGriffon"))).isEqualTo(stewieGriffon);
|
||||
assertThat(users.get(getKey(stewieGriffon))).isEqualTo(stewieGriffon);
|
||||
assertThat(this.users.containsValue(stewieGriffon)).isFalse();
|
||||
assertThat(this.usersTemplate.putIfAbsent(getKey(stewieGriffon), stewieGriffon)).isNull();
|
||||
assertThat(this.users.containsValue(stewieGriffon)).isTrue();
|
||||
assertThat(this.usersTemplate.putIfAbsent(getKey(stewieGriffon), newUser("megGriffon"))).isEqualTo(stewieGriffon);
|
||||
assertThat(this.users.get(getKey(stewieGriffon))).isEqualTo(stewieGriffon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove() {
|
||||
User mandyHandy = users.get(getKey(getUser("mandyHandy")));
|
||||
|
||||
User mandyHandy = this.users.get(getKey(getUser("mandyHandy")));
|
||||
|
||||
assertThat(mandyHandy).isNotNull();
|
||||
assertThat(usersTemplate.<Object, Object>remove(getKey(mandyHandy))).isEqualTo(mandyHandy);
|
||||
assertThat(users.containsKey(getKey(mandyHandy))).isFalse();
|
||||
assertThat(users.containsValue(mandyHandy)).isFalse();
|
||||
assertThat(users.containsKey("loisGriffon")).isFalse();
|
||||
assertThat(usersTemplate.<Object, Object>remove("loisGriffon")).isNull();
|
||||
assertThat(users.containsKey("loisGriffon")).isFalse();
|
||||
assertThat(this.usersTemplate.<Object, Object>remove(getKey(mandyHandy))).isEqualTo(mandyHandy);
|
||||
assertThat(this.users.containsKey(getKey(mandyHandy))).isFalse();
|
||||
assertThat(this.users.containsValue(mandyHandy)).isFalse();
|
||||
assertThat(this.users.containsKey("loisGriffon")).isFalse();
|
||||
assertThat(this.usersTemplate.<Object, Object>remove("loisGriffon")).isNull();
|
||||
assertThat(this.users.containsKey("loisGriffon")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replace() {
|
||||
User randyHandy = users.get(getKey(getUser("randyHandy")));
|
||||
|
||||
User randyHandy = this.users.get(getKey(getUser("randyHandy")));
|
||||
User lukeFluke = newUser("lukeFluke");
|
||||
User chrisGriffon = newUser("chrisGriffon");
|
||||
|
||||
assertThat(randyHandy).isNotNull();
|
||||
assertThat(usersTemplate.replace(getKey(randyHandy), lukeFluke)).isEqualTo(randyHandy);
|
||||
assertThat(users.get(getKey(randyHandy))).isEqualTo(lukeFluke);
|
||||
assertThat(users.containsValue(randyHandy)).isFalse();
|
||||
assertThat(users.containsValue(chrisGriffon)).isFalse();
|
||||
assertThat(usersTemplate.replace(getKey(chrisGriffon), chrisGriffon)).isNull();
|
||||
assertThat(users.containsValue(chrisGriffon)).isFalse();
|
||||
assertThat(this.usersTemplate.replace(getKey(randyHandy), lukeFluke)).isEqualTo(randyHandy);
|
||||
assertThat(this.users.get(getKey(randyHandy))).isEqualTo(lukeFluke);
|
||||
assertThat(this.users.containsValue(randyHandy)).isFalse();
|
||||
assertThat(this.users.containsValue(chrisGriffon)).isFalse();
|
||||
assertThat(this.usersTemplate.replace(getKey(chrisGriffon), chrisGriffon)).isNull();
|
||||
assertThat(this.users.containsValue(chrisGriffon)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceOldValueWithNewValue() {
|
||||
|
||||
User jackHandy = getUser("jackHandy");
|
||||
User imaPigg = getUser("imaPigg");
|
||||
|
||||
assertThat(users.containsValue(jackHandy)).isTrue();
|
||||
assertThat(usersTemplate.replace(getKey(jackHandy), null, imaPigg)).isFalse();
|
||||
assertThat(users.containsValue(jackHandy)).isTrue();
|
||||
assertThat(users.get(getKey(jackHandy))).isEqualTo(jackHandy);
|
||||
assertThat(usersTemplate.replace(getKey(jackHandy), jackHandy, imaPigg)).isTrue();
|
||||
assertThat(users.containsValue(jackHandy)).isFalse();
|
||||
assertThat(users.get(getKey(jackHandy))).isEqualTo(imaPigg);
|
||||
assertThat(this.users.containsValue(jackHandy)).isTrue();
|
||||
assertThat(this.usersTemplate.replace(getKey(jackHandy), null, imaPigg)).isFalse();
|
||||
assertThat(this.users.containsValue(jackHandy)).isTrue();
|
||||
assertThat(this.users.get(getKey(jackHandy))).isEqualTo(jackHandy);
|
||||
assertThat(this.usersTemplate.replace(getKey(jackHandy), jackHandy, imaPigg)).isTrue();
|
||||
assertThat(this.users.containsValue(jackHandy)).isFalse();
|
||||
assertThat(this.users.get(getKey(jackHandy))).isEqualTo(imaPigg);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllReturnsNoResults() {
|
||||
|
||||
List<String> keys = Arrays.asList("keyOne", "keyTwo", "keyThree");
|
||||
Map<String, User> users = usersTemplate.getAll(keys);
|
||||
|
||||
Map<String, User> users = this.usersTemplate.getAll(keys);
|
||||
|
||||
assertThat(users).isNotNull();
|
||||
assertThat(users).isEqualTo(this.users.getAll(keys));
|
||||
@@ -283,7 +308,8 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void getAllReturnsResults() {
|
||||
Map<String, User> users = usersTemplate.getAll(Arrays.asList(
|
||||
|
||||
Map<String, User> users = this.usersTemplate.getAll(Arrays.asList(
|
||||
getKey(getUser("jonDoe")), getKey(getUser("pieDoe"))));
|
||||
|
||||
assertThat(users).isNotNull();
|
||||
@@ -292,24 +318,26 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void putAll() {
|
||||
|
||||
User batMan = newUser("batMan");
|
||||
User spiderMan = newUser("spiderMan");
|
||||
User superMan = newUser("superMan");
|
||||
|
||||
Map<String, User> userMap = getUsersAsMap(batMan, spiderMan, superMan);
|
||||
|
||||
assertThat(users.keySet().containsAll(userMap.keySet())).isFalse();
|
||||
assertThat(users.values().containsAll(userMap.values())).isFalse();
|
||||
assertThat(this.users.keySet().containsAll(userMap.keySet())).isFalse();
|
||||
assertThat(this.users.values().containsAll(userMap.values())).isFalse();
|
||||
|
||||
usersTemplate.putAll(userMap);
|
||||
this.usersTemplate.putAll(userMap);
|
||||
|
||||
assertThat(users.keySet().containsAll(userMap.keySet())).isTrue();
|
||||
assertThat(users.values().containsAll(userMap.values())).isTrue();
|
||||
assertThat(this.users.keySet().containsAll(userMap.keySet())).isTrue();
|
||||
assertThat(this.users.values().containsAll(userMap.values())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void query() {
|
||||
SelectResults<User> queryResults = usersTemplate.query("username LIKE '%Doe'");
|
||||
|
||||
SelectResults<User> queryResults = this.usersTemplate.query("username LIKE '%Doe'");
|
||||
|
||||
assertThat(queryResults).isNotNull();
|
||||
|
||||
@@ -322,7 +350,9 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void find() {
|
||||
SelectResults<User> findResults = usersTemplate.find("SELECT u FROM /Users u WHERE u.username LIKE $1 AND u.active = $2", "%Doe", true);
|
||||
|
||||
SelectResults<User> findResults =
|
||||
this.usersTemplate.find("SELECT u FROM /Users u WHERE u.username LIKE $1 AND u.active = $2", "%Doe", true);
|
||||
|
||||
assertThat(findResults).isNotNull();
|
||||
|
||||
@@ -333,9 +363,46 @@ public class GemfireTemplateIntegrationTests {
|
||||
assertThat(usersFound.containsAll(getUsers("jonDoe", "cookieDoe"))).isTrue();
|
||||
}
|
||||
|
||||
// The following query is syntactically correct but does NOT work!!!
|
||||
// "SELECT keys FROM /Users u, u.keySet keys WHERE u.active = false ORDER BY u.username ASC"
|
||||
@Test
|
||||
public void findKeys() {
|
||||
|
||||
User mandyHandy = getUser("mandyHandy");
|
||||
|
||||
this.users.put(mandyHandy.getUsername(), mandyHandy);
|
||||
|
||||
String query = "SELECT u.key FROM /Users.entrySet u WHERE u.value.active = false ORDER BY u.value.username ASC";
|
||||
|
||||
SelectResults<String> results = this.usersTemplate.find(query);
|
||||
|
||||
assertThat(results).isNotNull();
|
||||
assertThat(results).hasSize(4);
|
||||
assertThat(results.asList()).containsExactly("janeDoe", "mandyHandy", "pieDoe", "randyHandy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findLimitedKeys() {
|
||||
|
||||
String query = "SELECT u.key"
|
||||
+ " FROM /Users.entrySet u"
|
||||
+ " WHERE u.value.active = false"
|
||||
+ " AND u.value.username LIKE '%Doe'"
|
||||
+ " ORDER BY u.value.username ASC"
|
||||
+ " LIMIT 1";
|
||||
|
||||
SelectResults<String> results = this.usersTemplate.find(query);
|
||||
|
||||
assertThat(results).isNotNull();
|
||||
assertThat(results).hasSize(1);
|
||||
assertThat(results.asList()).containsExactly("janeDoe");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findUniqueReturnsResult() {
|
||||
User jonDoe = usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username = $1", "jonDoe");
|
||||
|
||||
User jonDoe =
|
||||
this.usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username = $1", "jonDoe");
|
||||
|
||||
assertThat(jonDoe).isNotNull();
|
||||
assertThat(jonDoe).isEqualTo(getUser("jonDoe"));
|
||||
@@ -343,12 +410,12 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findUniqueReturnsNoResult() {
|
||||
usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username = $1", "benDover");
|
||||
this.usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username = $1", "benDover");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findUnqiueReturnsTooManyResults() {
|
||||
usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username LIKE $1", "%Doe");
|
||||
public void findUniqueReturnsTooManyResults() {
|
||||
this.usersTemplate.findUnique("SELECT u FROM /Users u WHERE u.username LIKE $1", "%Doe");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -374,6 +441,7 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
|
||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
||||
|
||||
gemfireCache.setClose(false);
|
||||
@@ -384,7 +452,8 @@ public class GemfireTemplateIntegrationTests {
|
||||
|
||||
@Bean(name = "Users")
|
||||
LocalRegionFactoryBean<Object, Object> usersRegion(GemFireCache gemfireCache) {
|
||||
LocalRegionFactoryBean<Object, Object> usersRegion = new LocalRegionFactoryBean<Object, Object>();
|
||||
|
||||
LocalRegionFactoryBean<Object, Object> usersRegion = new LocalRegionFactoryBean<>();
|
||||
|
||||
usersRegion.setCache(gemfireCache);
|
||||
usersRegion.setClose(false);
|
||||
@@ -394,7 +463,7 @@ public class GemfireTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
GemfireTemplate usersRegionTemplate(Region<Object, Object> simple) {
|
||||
GemfireTemplate usersTemplate(Region<Object, Object> simple) {
|
||||
return new GemfireTemplate(simple);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyCollection;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
import org.apache.geode.cache.query.types.CollectionType;
|
||||
import org.apache.geode.cache.query.types.ObjectType;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link AbstractSelectResults}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.junit.MockitoJUnitRunner
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.gemfire.repository.query.AbstractSelectResults
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public class AbstractSelectResultsUnitTests {
|
||||
|
||||
@Mock
|
||||
private SelectResults<Object> mockSelectResults;
|
||||
|
||||
@Test
|
||||
public void constructsAbstractSelectResultsSuccessfully() {
|
||||
|
||||
AbstractSelectResults<?> selectResults = new TestSelectResults(this.mockSelectResults);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isSameAs(this.mockSelectResults);
|
||||
|
||||
verifyNoInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructAbstractSelectResultsWithNullThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new TestSelectResults(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("SelectResults must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asListCallsSelectResultsAsList() {
|
||||
|
||||
List<Object> mockList = mock(List.class);
|
||||
|
||||
doReturn(mockList).when(this.mockSelectResults).asList();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).asList()).isEqualTo(mockList);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asList();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asSetCallsSelectResultsAsSet() {
|
||||
|
||||
Set<Object> mockSet = mock(Set.class);
|
||||
|
||||
doReturn(mockSet).when(this.mockSelectResults).asSet();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).asSet()).isEqualTo(mockSet);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asSet();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCollectionTypeCallsSelectResultsGetCollectionType() {
|
||||
|
||||
CollectionType mockCollectionType = mock(CollectionType.class);
|
||||
|
||||
doReturn(mockCollectionType).when(this.mockSelectResults).getCollectionType();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).getCollectionType()).isEqualTo(mockCollectionType);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).getCollectionType();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isModifiableCallsSelectResultsIsModifiable() {
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).isModifiable();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).isModifiable()).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).isModifiable();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void occurrencesCallsSelectResultsOccurrences() {
|
||||
|
||||
doReturn(5).when(this.mockSelectResults).occurrences(eq("test"));
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).occurrences("test")).isEqualTo(5);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).occurrences(eq("test"));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setObjectTypeCallsSelectResultsSetObjectType() {
|
||||
|
||||
ObjectType mockObjectType = mock(ObjectType.class);
|
||||
|
||||
new TestSelectResults(this.mockSelectResults).setElementType(mockObjectType);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).setElementType(eq(mockObjectType));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCallsSelectResultsAdd() {
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).add(any());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).add("test")).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).add(eq("test"));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAllCallsSelectResultsAddAll() {
|
||||
|
||||
Collection<Object> mockCollection = mock(Collection.class);
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).addAll(anyCollection());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).addAll(mockCollection)).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).addAll(eq(mockCollection));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCallsSelectResultsClear() {
|
||||
|
||||
new TestSelectResults(this.mockSelectResults).clear();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).clear();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsCallsSelectResultsContains() {
|
||||
|
||||
doReturn(false).when(this.mockSelectResults).contains(any());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).contains("test")).isFalse();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).contains(eq("test"));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsAllCallsSelectResultsContainsAll() {
|
||||
|
||||
Collection<Object> mockCollection = mock(Collection.class);
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).containsAll(anyCollection());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).containsAll(mockCollection)).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).containsAll(eq(mockCollection));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmptyCallsSelectResultsIsEmpty() {
|
||||
|
||||
doReturn(false).when(this.mockSelectResults).isEmpty();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).isEmpty()).isFalse();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).isEmpty();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorCallsSelectResultsIterator() {
|
||||
|
||||
Iterator<Object> mockIterator = mock(Iterator.class);
|
||||
|
||||
doReturn(mockIterator).when(this.mockSelectResults).iterator();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).iterator()).isEqualTo(mockIterator);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).iterator();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeCallsSelectResultsRemove() {
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).remove(any());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).remove("mock")).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).remove(eq("mock"));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAllCallsSelectResultsRemoveAll() {
|
||||
|
||||
Collection<Object> mockCollection = mock(Collection.class);
|
||||
|
||||
doReturn(false).when(this.mockSelectResults).removeAll(anyCollection());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).removeAll(mockCollection)).isFalse();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).removeAll(eq(mockCollection));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retainAllCallsSelectResultsRetainAll() {
|
||||
|
||||
Collection<Object> mockCollection = mock(Collection.class);
|
||||
|
||||
doReturn(true).when(this.mockSelectResults).retainAll(anyCollection());
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).retainAll(mockCollection)).isTrue();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).retainAll(eq(mockCollection));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sizeCallsSelectResultsSize() {
|
||||
|
||||
doReturn(50).when(this.mockSelectResults).size();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).size()).isEqualTo(50);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).size();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toArrayCallsSelectResultsToArray() {
|
||||
|
||||
Object[] array = { "test", "mock" };
|
||||
|
||||
doReturn(array).when(this.mockSelectResults).toArray();
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).toArray()).isEqualTo(array);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).toArray();
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toArrayWithArrayArgumentCallsSelectResultsToArray() {
|
||||
|
||||
Object[] array = { "test", "mock" };
|
||||
|
||||
doReturn(array).when(this.mockSelectResults).toArray(eq(new Object[0]));
|
||||
|
||||
assertThat(new TestSelectResults(this.mockSelectResults).toArray(new Object[0])).isEqualTo(array);
|
||||
|
||||
verify(this.mockSelectResults, times(1)).toArray(eq(new Object[0]));
|
||||
verifyNoMoreInteractions(this.mockSelectResults);
|
||||
}
|
||||
|
||||
static class TestSelectResults extends AbstractSelectResults<Object> {
|
||||
|
||||
TestSelectResults(@NonNull SelectResults<Object> selectResults) {
|
||||
super(selectResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link PagedSelectResults}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.junit.MockitoJUnitRunner
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.gemfire.repository.query.PagedSelectResults
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PagedSelectResultsUnitTests {
|
||||
|
||||
@Mock
|
||||
private Pageable mockPageable;
|
||||
|
||||
@Mock
|
||||
private SelectResults<Object> mockSelectResults;
|
||||
|
||||
@Test
|
||||
public void constructPagedSelectResultsIsCorrect() {
|
||||
|
||||
PagedSelectResults<Object> selectResults = new PagedSelectResults<>(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isSameAs(this.mockSelectResults);
|
||||
assertThat(selectResults.getPageRequest()).isSameAs(this.mockPageable);
|
||||
|
||||
verifyNoInteractions(this.mockPageable, mockSelectResults);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructPagedSelectResultsWithNullPageableThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new PagedSelectResults<>(this.mockSelectResults, null);
|
||||
}
|
||||
catch(IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Pageable must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verifyNoInteractions(this.mockSelectResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructPagedSelectResultsWithNullSelectResultsThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new PagedSelectResults<>(null, this.mockPageable);
|
||||
}
|
||||
catch(IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("SelectResults must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verifyNoInteractions(this.mockPageable);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asListIsCorrect() {
|
||||
|
||||
List<String> names = Arrays.asList("Jon Doe", "Jane Doe", "Cookie Doe", "Pie Doe", "Sour Doe");
|
||||
|
||||
doReturn(names).when(this.mockSelectResults).asList();
|
||||
doReturn(1).when(this.mockPageable).getPageNumber();
|
||||
doReturn(2).when(this.mockPageable).getPageSize();
|
||||
|
||||
PagedSelectResults<Object> selectResults = new PagedSelectResults<>(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isEqualTo(this.mockSelectResults);
|
||||
assertThat(selectResults.getPageRequest()).isEqualTo(this.mockPageable);
|
||||
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
List<Object> pagedNames = selectResults.asList();
|
||||
|
||||
assertThat(pagedNames).isNotNull();
|
||||
assertThat(pagedNames).hasSize(2);
|
||||
assertThat(pagedNames).containsExactly("Cookie Doe", "Pie Doe");
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asList();
|
||||
verify(this.mockPageable, times(2)).getPageNumber();
|
||||
verify(this.mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(this.mockSelectResults, this.mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asPageListIsCorrect() {
|
||||
|
||||
List<String> names = Arrays.asList("Jon Doe", "Jane Doe", "Cookie Doe", "Pie Doe", "Sour Doe");
|
||||
|
||||
doReturn(names).when(this.mockSelectResults).asList();
|
||||
doReturn(0).when(this.mockPageable).getPageNumber();
|
||||
doReturn(3).when(this.mockPageable).getPageSize();
|
||||
|
||||
PagedSelectResults<Object> selectResults = new PagedSelectResults<>(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isEqualTo(this.mockSelectResults);
|
||||
assertThat(selectResults.getPageRequest()).isEqualTo(this.mockPageable);
|
||||
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
List<Object> pagedNames = selectResults.asList();
|
||||
|
||||
assertThat(pagedNames).isNotNull();
|
||||
assertThat(pagedNames).hasSize(3);
|
||||
assertThat(pagedNames).containsExactly("Jon Doe", "Jane Doe", "Cookie Doe");
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asList();
|
||||
verify(this.mockPageable, times(2)).getPageNumber();
|
||||
verify(this.mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(this.mockSelectResults, this.mockPageable);
|
||||
reset(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
// Page 2
|
||||
doReturn(names).when(this.mockSelectResults).asList();
|
||||
doReturn(1).when(this.mockPageable).getPageNumber();
|
||||
doReturn(3).when(this.mockPageable).getPageSize();
|
||||
|
||||
selectResults = selectResults.with(this.mockPageable);
|
||||
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isEqualTo(this.mockSelectResults);
|
||||
assertThat(selectResults.getPageRequest()).isEqualTo(this.mockPageable);
|
||||
|
||||
pagedNames = selectResults.asList();
|
||||
|
||||
assertThat(pagedNames).isNotNull();
|
||||
assertThat(pagedNames).hasSize(2);
|
||||
assertThat(pagedNames).containsExactly("Pie Doe", "Sour Doe");
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asList();
|
||||
verify(this.mockPageable, times(2)).getPageNumber();
|
||||
verify(this.mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(this.mockSelectResults, this.mockPageable);
|
||||
reset(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
// Page 3
|
||||
doReturn(names).when(this.mockSelectResults).asList();
|
||||
doReturn(2).when(this.mockPageable).getPageNumber();
|
||||
doReturn(3).when(this.mockPageable).getPageSize();
|
||||
|
||||
selectResults = selectResults.with(this.mockPageable);
|
||||
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
|
||||
assertThat(selectResults).isNotNull();
|
||||
assertThat(selectResults.getSelectResults()).isEqualTo(this.mockSelectResults);
|
||||
assertThat(selectResults.getPageRequest()).isEqualTo(this.mockPageable);
|
||||
|
||||
pagedNames = selectResults.asList();
|
||||
|
||||
assertThat(pagedNames).isNotNull();
|
||||
assertThat(pagedNames).isEmpty();
|
||||
|
||||
verify(this.mockSelectResults, times(1)).asList();
|
||||
verify(this.mockPageable, times(2)).getPageNumber();
|
||||
verify(this.mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(this.mockSelectResults, this.mockPageable);
|
||||
reset(this.mockSelectResults, this.mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asSetCallsAsList() {
|
||||
|
||||
List<Object> list = Collections.singletonList("mock");
|
||||
|
||||
PagedSelectResults<Object> selectResults =
|
||||
spy(new PagedSelectResults<>(this.mockSelectResults, this.mockPageable));
|
||||
|
||||
doReturn(list).when(selectResults).asList();
|
||||
|
||||
assertThat(selectResults.asSet()).containsExactly("mock");
|
||||
|
||||
verify(selectResults, times(1)).asList();
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void iteratorCallsAsList() {
|
||||
|
||||
List<Object> list = Collections.singletonList("test");
|
||||
|
||||
PagedSelectResults<Object> selectResults =
|
||||
spy(new PagedSelectResults<>(this.mockSelectResults, this.mockPageable));
|
||||
|
||||
doReturn(list).when(selectResults).asList();
|
||||
|
||||
Iterator<Object> iterator = selectResults.iterator();
|
||||
|
||||
assertThat(iterator).isNotNull();
|
||||
assertThat(iterator.hasNext()).isTrue();
|
||||
assertThat(iterator.next()).isEqualTo("test");
|
||||
assertThat(iterator.hasNext()).isFalse();
|
||||
|
||||
try {
|
||||
iterator.remove();
|
||||
}
|
||||
finally {
|
||||
verify(selectResults, times(1)).asList();
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sizeCallsAsList() {
|
||||
|
||||
List<Object> mockList = mock(List.class);
|
||||
|
||||
doReturn(50).when(mockList).size();
|
||||
|
||||
PagedSelectResults<Object> selectResults =
|
||||
spy(new PagedSelectResults<>(this.mockSelectResults, this.mockPageable));
|
||||
|
||||
doReturn(mockList).when(selectResults).asList();
|
||||
|
||||
assertThat(selectResults.size()).isEqualTo(50);
|
||||
|
||||
verify(selectResults, times(1)).asList();
|
||||
verify(mockList, times(1)).size();
|
||||
verifyNoMoreInteractions(mockList);
|
||||
verifyNoInteractions(this.mockSelectResults, this.mockPageable);
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.data.gemfire.repository.query.QueryString.HINT_PATTERN;
|
||||
import static org.springframework.data.gemfire.repository.query.QueryString.IMPORT_PATTERN;
|
||||
@@ -28,37 +28,44 @@ import static org.springframework.data.gemfire.repository.query.QueryString.TRAC
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.repository.sample.Person;
|
||||
import org.springframework.data.gemfire.repository.sample.RootUser;
|
||||
import org.springframework.data.gemfire.test.model.Person;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Test suite of test cases testing the contract and functionality of the {@link QueryString} class.
|
||||
* Unit Tests for {@link QueryString}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see java.util.regex.Pattern
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.mockito.junit.MockitoJUnitRunner
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QueryStringUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("rawtypes")
|
||||
Region region;
|
||||
private Region region;
|
||||
|
||||
private Sort.Order newSortOrder(String property) {
|
||||
return newSortOrder(property, Sort.Direction.ASC);
|
||||
@@ -72,14 +79,19 @@ public class QueryStringUnitTests {
|
||||
return Sort.by(orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructQueryStringWithAtRegionAnnotatedDomainType() {
|
||||
assertThat(new QueryString(Person.class).toString()).isEqualTo("SELECT * FROM /People");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructQueryStringWithDomainType() {
|
||||
assertThat(new QueryString(Person.class).toString()).isEqualTo("SELECT * FROM /Person");
|
||||
assertThat(new QueryString(User.class).toString()).isEqualTo("SELECT * FROM /User");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructQueryStringWithDomainTypeAsCount() {
|
||||
assertThat(new QueryString(Person.class, true).toString()).isEqualTo("SELECT count(*) FROM /Person");
|
||||
assertThat(new QueryString(User.class, true).toString()).isEqualTo("SELECT count(*) FROM /User");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -107,20 +119,20 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithBlankQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException(" ");
|
||||
assertInvalidQueryThrowsIllegalArgumentException(" ");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithEmptyQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException("");
|
||||
assertInvalidQueryThrowsIllegalArgumentException("");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithNullQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException(null);
|
||||
assertInvalidQueryThrowsIllegalArgumentException(null);
|
||||
}
|
||||
|
||||
private void assertUnspecifiedQueryThrowsIllegalArgumentException(String query) {
|
||||
private void assertInvalidQueryThrowsIllegalArgumentException(String query) {
|
||||
|
||||
try {
|
||||
new QueryString(query);
|
||||
@@ -144,21 +156,94 @@ public class QueryStringUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringFromDomainType() {
|
||||
public void queryStringFromAtRegionAnnotatedDomainType() {
|
||||
|
||||
QueryString query = QueryString.from(Person.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /Person");
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /People");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringFromDomainType() {
|
||||
|
||||
QueryString query = QueryString.from(User.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /User");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringCountingObjectsOfAtAnnotatedDomainType() {
|
||||
|
||||
QueryString query = QueryString.count(Person.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT count(*) FROM /People");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringCountingObjectsOfDomainType() {
|
||||
|
||||
QueryString query = QueryString.count(Person.class);
|
||||
QueryString query = QueryString.count(User.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT count(*) FROM /Person");
|
||||
assertThat(query.toString()).isEqualTo("SELECT count(*) FROM /User");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDigitsOnlyIsCorrect() {
|
||||
|
||||
assertThat(QueryString.getDigitsOnly("1")).isEqualTo("1");
|
||||
assertThat(QueryString.getDigitsOnly(" 2")).isEqualTo("2");
|
||||
assertThat(QueryString.getDigitsOnly(" 2 34 ")).isEqualTo("234");
|
||||
assertThat(QueryString.getDigitsOnly("abc123")).isEqualTo("123");
|
||||
assertThat(QueryString.getDigitsOnly("O1E2l4")).isEqualTo("124");
|
||||
assertThat(QueryString.getDigitsOnly("lOlO")).isEqualTo("");
|
||||
assertThat(QueryString.getDigitsOnly(" ")).isEqualTo("");
|
||||
assertThat(QueryString.getDigitsOnly("")).isEqualTo("");
|
||||
assertThat(QueryString.getDigitsOnly(null)).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLimitedWithLimitBasedQueryReturnsTrue() {
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT 50").isLimited()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLimitedWithUnlimitedQueryReturnsFalse() {
|
||||
assertThat(QueryString.of("SELECT * FROM /Test").isLimited()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLimitedWithQueryHavingInvalidLimitSyntaxReturnsFalse() {
|
||||
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT").isLimited()).isFalse();
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT abc").isLimited()).isFalse();
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT abc123").isLimited()).isFalse();
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT lO").isLimited()).isFalse();
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LMT 10").isLimited()).isFalse();
|
||||
assertThat(QueryString.of("SELECT * FROM /Test 10").isLimited()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLimitReturnsIntegerValue() {
|
||||
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT 1").getLimit()).isEqualTo(1);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT 10").getLimit()).isEqualTo(10);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT 21").getLimit()).isEqualTo(21);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT 421").getLimit()).isEqualTo(421);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLimitReturnsIntegerMaxValue() {
|
||||
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT abc").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT abc123").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LIMIT lO").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test LMT 10").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(QueryString.of("SELECT * FROM /Test 10").getLimit()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -244,6 +329,38 @@ public class QueryStringUnitTests {
|
||||
return pattern.matcher(value).find();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustLimitWithQueryHavingLimit() {
|
||||
|
||||
QueryString original = QueryString.of("SELECT * FROM /Test LIMIT 10");
|
||||
QueryString adjusted = original.adjustLimit(20);
|
||||
|
||||
assertThat(adjusted).isNotNull();
|
||||
assertThat(adjusted).isNotSameAs(original);
|
||||
assertThat(adjusted.toString()).isEqualTo("SELECT * FROM /Test LIMIT 20");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustLimitWithQueryHavingNoLimit() {
|
||||
|
||||
QueryString original = QueryString.of("SELECT * FROM /Test");
|
||||
QueryString adjusted = original.adjustLimit(25);
|
||||
|
||||
assertThat(adjusted).isNotNull();
|
||||
assertThat(adjusted).isNotSameAs(original);
|
||||
assertThat(adjusted.toString()).isEqualTo("SELECT * FROM /Test LIMIT 25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustLimitWithNullLimit() {
|
||||
|
||||
QueryString original = QueryString.of("SELECT * FROM /Test LIMIT 10");
|
||||
QueryString adjusted = original.adjustLimit(null);
|
||||
|
||||
assertThat(adjusted).isSameAs(original);
|
||||
assertThat(adjusted.toString()).isEqualTo("SELECT * FROM /Test LIMIT 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asDistinctQuery() {
|
||||
|
||||
@@ -268,10 +385,11 @@ public class QueryStringUnitTests {
|
||||
|
||||
when(this.region.getFullPath()).thenReturn("/foo/bar");
|
||||
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /Person");
|
||||
assertThat(query.fromRegion(Person.class, this.region).toString()).isEqualTo("SELECT * FROM /foo/bar");
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /People");
|
||||
assertThat(query.fromRegion(this.region, Person.class).toString()).isEqualTo("SELECT * FROM /foo/bar");
|
||||
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(this.region);
|
||||
}
|
||||
|
||||
// SGF-156, SGF-251
|
||||
@@ -282,10 +400,11 @@ public class QueryStringUnitTests {
|
||||
|
||||
when(this.region.getFullPath()).thenReturn("/People");
|
||||
|
||||
assertThat(query.fromRegion(Person.class, this.region).toString())
|
||||
assertThat(query.fromRegion(this.region, Person.class).toString())
|
||||
.isEqualTo("SELECT * FROM /People p WHERE p.lastname = $1");
|
||||
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(this.region);
|
||||
}
|
||||
|
||||
// SGF-252
|
||||
@@ -296,10 +415,11 @@ public class QueryStringUnitTests {
|
||||
|
||||
when(this.region.getFullPath()).thenReturn("/Remote/Root/Users");
|
||||
|
||||
assertThat(query.fromRegion(RootUser.class, this.region).toString())
|
||||
assertThat(query.fromRegion(this.region, RootUser.class).toString())
|
||||
.isEqualTo("SELECT * FROM /Remote/Root/Users u WHERE u.username = $1");
|
||||
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(this.region);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -428,4 +548,18 @@ public class QueryStringUnitTests {
|
||||
assertThat(query.toString())
|
||||
.isEqualTo("<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 20");
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString(of = "name")
|
||||
@EqualsAndHashCode(of = "name")
|
||||
@RequiredArgsConstructor(staticName = "newUser")
|
||||
@SuppressWarnings("unused")
|
||||
static class User {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link OqlQueryExecutor}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlQueryExecutor
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class OqlQueryExecutorUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void newUnsupportedQueryExecutionExceptionIsCorrect() {
|
||||
|
||||
OqlQueryExecutor mockQueryExecutor = mock(OqlQueryExecutor.class);
|
||||
|
||||
doCallRealMethod().when(mockQueryExecutor).newUnsupportedQueryExecutionException(anyString());
|
||||
|
||||
UnsupportedQueryExecutionException exception =
|
||||
mockQueryExecutor.newUnsupportedQueryExecutionException("SELECT * FROM /TestRegion");
|
||||
|
||||
assertThat(exception).isNotNull();
|
||||
assertThat(exception).hasMessage(OqlQueryExecutor.NON_EXECUTABLE_QUERY_MESSAGE,
|
||||
"SELECT * FROM /TestRegion", mockQueryExecutor.getClass().getName());
|
||||
assertThat(exception).hasNoCause();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thenExecuteWithComposesOqlQueryExecutorsCorrectly() {
|
||||
|
||||
OqlQueryExecutor one = mock(OqlQueryExecutor.class);
|
||||
OqlQueryExecutor two = mock(OqlQueryExecutor.class);
|
||||
|
||||
doCallRealMethod().when(one).thenExecuteWith(any());
|
||||
|
||||
assertThat(one.thenExecuteWith(null)).isSameAs(one);
|
||||
|
||||
OqlQueryExecutor composed = one.thenExecuteWith(two);
|
||||
|
||||
assertThat(composed).isNotNull();
|
||||
assertThat(composed).isNotSameAs(one);
|
||||
assertThat(composed).isNotSameAs(two);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composedOqlQueryExecutorExecutesOne() {
|
||||
|
||||
String query = "SELECT * FROM /TestRegion";
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
OqlQueryExecutor one = mock(OqlQueryExecutor.class);
|
||||
OqlQueryExecutor two = mock(OqlQueryExecutor.class);
|
||||
|
||||
doCallRealMethod().when(one).thenExecuteWith(any());
|
||||
|
||||
OqlQueryExecutor composed = one.thenExecuteWith(two);
|
||||
|
||||
assertThat(composed).isNotNull();
|
||||
|
||||
composed.execute(mockQueryMethod, query, "test");
|
||||
|
||||
verify(one, times(1)).execute(eq(mockQueryMethod), eq(query), eq("test"));
|
||||
verifyNoInteractions(two);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void composedOqlQueryExecutorExecutesOneThenShortCircuitsWhenExceptionIsThrown() {
|
||||
|
||||
String query = "SELECT * FROM /TestRegion";
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
OqlQueryExecutor one = mock(OqlQueryExecutor.class);
|
||||
OqlQueryExecutor two = mock(OqlQueryExecutor.class);
|
||||
|
||||
doCallRealMethod().when(one).thenExecuteWith(any());
|
||||
doThrow(newIllegalArgumentException("test"))
|
||||
.when(one).execute(any(QueryMethod.class), anyString(), any());
|
||||
|
||||
OqlQueryExecutor composed = one.thenExecuteWith(two);
|
||||
|
||||
assertThat(composed).isNotNull();
|
||||
|
||||
try {
|
||||
composed.execute(mockQueryMethod, query, "junk");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("test");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(one, times(1)).execute(eq(mockQueryMethod), eq(query), eq("junk"));
|
||||
verifyNoInteractions(two);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composedOqlQueryExecutorExecutesTwoWhenOneThrowsUnsupportedQueryExecutionException() {
|
||||
|
||||
String query = "SELECT * FROM /TestRegion";
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
OqlQueryExecutor one = mock(OqlQueryExecutor.class);
|
||||
OqlQueryExecutor two = mock(OqlQueryExecutor.class);
|
||||
|
||||
doCallRealMethod().when(one).thenExecuteWith(any());
|
||||
doThrow(new UnsupportedQueryExecutionException("test"))
|
||||
.when(one).execute(any(QueryMethod.class), anyString(), any());
|
||||
|
||||
OqlQueryExecutor composed = one.thenExecuteWith(two);
|
||||
|
||||
assertThat(composed).isNotNull();
|
||||
|
||||
composed.execute(mockQueryMethod, query, "mock");
|
||||
|
||||
verify(one, times(1)).execute(eq(mockQueryMethod), eq(query), eq("mock"));
|
||||
verify(two, times(1)).execute(eq(mockQueryMethod), eq(query), eq("mock"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.data.gemfire.repository.query.GemfireQueryMethod;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.test.context.event.annotation.AfterTestClass;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link PagingUtils}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.List
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.gemfire.repository.query.support.PagingUtils
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class PagingUtilsUnitTests {
|
||||
|
||||
@AfterTestClass
|
||||
public static void tearDown() {
|
||||
PagingUtils.isPageQueryFunction = PagingUtils.DEFAULT_IS_PAGE_QUERY_FUNCTION;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertPageableIsCorrect() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(10).when(mockPageable).getPageSize();
|
||||
|
||||
PagingUtils.assertPageable(mockPageable);
|
||||
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verify(mockPageable, times(1)).getPageSize();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void assertPageableWithInvalidPageNumberThrowsIllegalArgumentException() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(-1).when(mockPageable).getPageNumber();
|
||||
|
||||
try {
|
||||
PagingUtils.assertPageable(mockPageable);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage(PagingUtils.INVALID_PAGE_NUMBER_MESSAGE, -1);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verify(mockPageable, never()).getPageSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void assertPageableWithInvalidPageSizeThrowsIllegalArgumentException() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber();
|
||||
doReturn(0).when(mockPageable).getPageSize();
|
||||
|
||||
try {
|
||||
PagingUtils.assertPageable(mockPageable);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage(PagingUtils.INVALID_PAGE_SIZE_MESSAGE, 0);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verify(mockPageable, times(1)).getPageSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void assertPageableWithNull() {
|
||||
|
||||
try {
|
||||
PagingUtils.assertPageable(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage(PagingUtils.NON_NULL_PAGEABLE_MESSAGE);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPageOneForPageOneReturnsTrue() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
|
||||
assertThat(PagingUtils.isPageOne(mockPageable)).isTrue();
|
||||
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPageOneForPageTwoReturnsFalse() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber();
|
||||
|
||||
assertThat(PagingUtils.isPageOne(mockPageable)).isFalse();
|
||||
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPageOneWithNullReturnsFalse() {
|
||||
assertThat(PagingUtils.isPageOne(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPagingPresentForQueryMethodReturningPageIsTrue() {
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
PagingUtils.isPageQueryFunction = queryMethod -> true;
|
||||
|
||||
assertThat(PagingUtils.isPagingPresent(mockQueryMethod)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPagingPresentForQueryMethodWithPageableParameterIsTrue() {
|
||||
|
||||
Parameters<?, ?> mockParameters = mock(Parameters.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
doReturn(mockParameters).when(mockQueryMethod).getParameters();
|
||||
doReturn(true).when(mockParameters).hasPageableParameter();
|
||||
|
||||
PagingUtils.isPageQueryFunction = queryMethod -> false;
|
||||
|
||||
assertThat(PagingUtils.isPagingPresent(mockQueryMethod)).isTrue();
|
||||
|
||||
verify(mockQueryMethod, times(1)).getParameters();
|
||||
verify(mockParameters, times(1)).hasPageableParameter();
|
||||
verifyNoMoreInteractions(mockQueryMethod, mockParameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPagingPresentForNonQueryMethodIsFalse() {
|
||||
|
||||
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class);
|
||||
|
||||
PagingUtils.isPageQueryFunction = queryMethod -> false;
|
||||
|
||||
doReturn(null).when(mockQueryMethod).getParameters();
|
||||
|
||||
assertThat(PagingUtils.isPagingPresent(mockQueryMethod)).isFalse();
|
||||
|
||||
verify(mockQueryMethod, times(1)).getParameters();
|
||||
verifyNoMoreInteractions(mockQueryMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPagingPresentForNullQueryMethodIsFalse() {
|
||||
assertThat(PagingUtils.isPagingPresent(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toPageIsCorrect() {
|
||||
|
||||
List<User> users = Arrays.asList(
|
||||
User.newUser("Jon Doe"),
|
||||
User.newUser("Jane Doe"),
|
||||
User.newUser("Cookie Doe"),
|
||||
User.newUser("Fro Doe"),
|
||||
User.newUser("Joe Doe"),
|
||||
User.newUser("Lan Doe"),
|
||||
User.newUser("Pie Doe"),
|
||||
User.newUser("Play Doe"),
|
||||
User.newUser("Sour Doe")
|
||||
);
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
Sort orderBy = Sort.by("name").ascending();
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
doReturn(orderBy).when(mockPageable).getSort();
|
||||
|
||||
Page<User> pageOne = PagingUtils.toPage(users, mockPageable);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isNotEmpty();
|
||||
assertThat(pageOne.getNumber()).isEqualTo(0);
|
||||
assertThat(pageOne.getNumberOfElements()).isEqualTo(5);
|
||||
assertThat(pageOne.getSize()).isEqualTo(5);
|
||||
assertThat(pageOne.getSort()).isEqualTo(orderBy);
|
||||
assertThat(pageOne.getTotalElements()).isEqualTo(users.size());
|
||||
assertThat(pageOne.getTotalPages()).isEqualTo(2);
|
||||
assertThat(pageOne.getContent()).containsExactly(users.subList(0, 5).toArray(new User[0]));
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber();
|
||||
|
||||
Page<User> pageTwo = PagingUtils.toPage(users, mockPageable);
|
||||
|
||||
assertThat(pageTwo).isNotNull();
|
||||
assertThat(pageTwo).isNotEmpty();
|
||||
assertThat(pageTwo.getNumber()).isEqualTo(1);
|
||||
assertThat(pageTwo.getNumberOfElements()).isEqualTo(4);
|
||||
assertThat(pageTwo.getSize()).isEqualTo(5);
|
||||
assertThat(pageTwo.getSort()).isEqualTo(orderBy);
|
||||
assertThat(pageTwo.getTotalElements()).isEqualTo(users.size());
|
||||
assertThat(pageTwo.getTotalPages()).isEqualTo(2);
|
||||
assertThat(pageTwo.getContent()).containsExactly(users.subList(5, users.size()).toArray(new User[0]));
|
||||
|
||||
doReturn(2).when(mockPageable).getPageNumber();
|
||||
|
||||
Page<User> pageThree = PagingUtils.toPage(users, mockPageable);
|
||||
|
||||
assertThat(pageThree).isNotNull();
|
||||
assertThat(pageThree).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPagedListFromListWithSizeLessThanPageSize() {
|
||||
|
||||
List<User> users = Arrays.asList(
|
||||
User.newUser("Jon Doe"),
|
||||
User.newUser("Jane Doe"),
|
||||
User.newUser("Pie Doe")
|
||||
);
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
List<User> pageOne = PagingUtils.getPagedList(users, mockPageable);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isNotEmpty();
|
||||
assertThat(pageOne).containsExactly(users.toArray(new User[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPagedListFromListResultingInEmptyPage() {
|
||||
|
||||
List<User> users = Arrays.asList(
|
||||
User.newUser("Jon Doe"),
|
||||
User.newUser("Jane Doe"),
|
||||
User.newUser("Pie Doe")
|
||||
);
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(1).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
List<User> pageOne = PagingUtils.getPagedList(users, mockPageable);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPagedListFromEmptyList() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
List<User> pageOne = PagingUtils.getPagedList(Collections.emptyList(), mockPageable);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPagedListFromNullList() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
List<User> pageOne = PagingUtils.getPagedList(null, mockPageable);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPagedListFromNullPageable() {
|
||||
|
||||
List<User> pageOne = PagingUtils.getPagedList(Collections.singletonList(User.newUser("Jon Doe")), null);
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPageRequestIsCorrect() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
Object[] arguments = { "test", mockPageable, "mock" };
|
||||
|
||||
Parameters<?, ?> mockParameters = mock(Parameters.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
Stream<?> mockStream = mock(Stream.class);
|
||||
|
||||
doReturn(mockParameters).when(mockQueryMethod).getParameters();
|
||||
doReturn(1).when(mockParameters).getPageableIndex();
|
||||
doReturn(true).when(mockParameters).hasPageableParameter();
|
||||
doReturn(mockStream).when(mockParameters).stream();
|
||||
doReturn(3L).when(mockStream).count();
|
||||
|
||||
Pageable pageable = PagingUtils.getPageRequest(mockQueryMethod, arguments);
|
||||
|
||||
assertThat(pageable).isSameAs(mockPageable);
|
||||
|
||||
InOrder order = inOrder(mockQueryMethod, mockParameters, mockStream);
|
||||
|
||||
order.verify(mockQueryMethod, times(1)).getParameters();
|
||||
order.verify(mockParameters, times(1)).hasPageableParameter();
|
||||
order.verify(mockParameters, times(1)).stream();
|
||||
order.verify(mockStream, times(1)).count();
|
||||
order.verify(mockParameters, times(1)).getPageableIndex();
|
||||
verifyNoMoreInteractions(mockQueryMethod, mockParameters, mockStream);
|
||||
verifyNoInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPageRequestWithNullQueryMethodThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
PagingUtils.getPageRequest(null, "test", "mock");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("QueryMethod must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void getPageRequestFromQueryMethodHavingNoPageableParameter() {
|
||||
|
||||
Parameters<?, ?> mockParameters = mock(Parameters.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
doReturn(mockParameters).when(mockQueryMethod).getParameters();
|
||||
doReturn(false).when(mockParameters).hasPageableParameter();
|
||||
|
||||
try {
|
||||
PagingUtils.getPageRequest(mockQueryMethod, "test", "mock");
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected)
|
||||
.hasMessage("QueryMethod [%s] does not have a Pageable parameter", mockQueryMethod);
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockQueryMethod, times(1)).getParameters();
|
||||
verify(mockParameters, times(1)).hasPageableParameter();
|
||||
verifyNoMoreInteractions(mockQueryMethod, mockParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPageRequestFromQueryMethodHavingLessParametersThanArgumentsThrowsIllegalArgumentException() {
|
||||
|
||||
Object[] arguments = { 1, 2, 3 };
|
||||
|
||||
Parameters<?, ?> mockParameters = mock(Parameters.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
Stream<?> mockStream = mock(Stream.class);
|
||||
|
||||
doReturn(mockParameters).when(mockQueryMethod).getParameters();
|
||||
doReturn(true).when(mockParameters).hasPageableParameter();
|
||||
doReturn(mockStream).when(mockParameters).stream();
|
||||
doReturn(2L).when(mockStream).count();
|
||||
|
||||
try {
|
||||
PagingUtils.getPageRequest(mockQueryMethod, arguments);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected)
|
||||
.hasMessage("The number of arguments [%d] must match the number of QueryMethod [%s] parameters [2]",
|
||||
arguments.length, mockQueryMethod);
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockQueryMethod, times(1)).getParameters();
|
||||
verify(mockParameters, times(1)).hasPageableParameter();
|
||||
verify(mockParameters, times(1)).stream();
|
||||
verify(mockStream, times(1)).count();
|
||||
verifyNoMoreInteractions(mockQueryMethod, mockParameters, mockStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPageRequestFromQueryMethodWherePageableParameterIndexDoesMatchPageableArgument() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
Object[] arguments = { mockPageable, "test", -1 };
|
||||
|
||||
Parameters<?, ?> mockParameters = mock(Parameters.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
Stream<?> mockStream = mock(Stream.class);
|
||||
|
||||
doReturn(mockParameters).when(mockQueryMethod).getParameters();
|
||||
doReturn(1).when(mockParameters).getPageableIndex();
|
||||
doReturn(true).when(mockParameters).hasPageableParameter();
|
||||
doReturn(mockStream).when(mockParameters).stream();
|
||||
doReturn(3L).when(mockStream).count();
|
||||
|
||||
try {
|
||||
PagingUtils.getPageRequest(mockQueryMethod, arguments);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Argument [test] must be of type [%2$s]", Pageable.class.getName());
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockQueryMethod, times(1)).getParameters();
|
||||
verify(mockParameters, times(1)).hasPageableParameter();
|
||||
verify(mockParameters, times(1)).stream();
|
||||
verify(mockStream, times(1)).count();
|
||||
verify(mockParameters, times(1)).getPageableIndex();
|
||||
verifyNoMoreInteractions(mockQueryMethod, mockParameters, mockStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetStartIndexForPageIsCorrect() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).doReturn(1).doReturn(2).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
assertThat(PagingUtils.getQueryResultSetStartIndexForPage(mockPageable)).isZero();
|
||||
assertThat(PagingUtils.getQueryResultSetStartIndexForPage(mockPageable)).isEqualTo(5);
|
||||
assertThat(PagingUtils.getQueryResultSetStartIndexForPage(mockPageable)).isEqualTo(10);
|
||||
|
||||
verify(mockPageable, times(3)).getPageNumber();
|
||||
verify(mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetStartIndexForPageWithNullPageableIsCorrect() {
|
||||
assertThat(PagingUtils.getQueryResultSetStartIndexForPage(null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetEndIndexForPageIsCorrect() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).doReturn(1).doReturn(2).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
assertThat(PagingUtils.getQueryResultSetEndIndexForPage(mockPageable)).isEqualTo(5);
|
||||
assertThat(PagingUtils.getQueryResultSetEndIndexForPage(mockPageable)).isEqualTo(10);
|
||||
assertThat(PagingUtils.getQueryResultSetEndIndexForPage(mockPageable)).isEqualTo(15);
|
||||
|
||||
verify(mockPageable, times(3)).getPageNumber();
|
||||
verify(mockPageable, times(6)).getPageSize();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetEndIndexForPageWithNullPageableIsCorrect() {
|
||||
assertThat(PagingUtils.getQueryResultSetEndIndexForPage(null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetLimitForPageIsCorrect() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).doReturn(1).doReturn(2).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
assertThat(PagingUtils.getQueryResultSetLimitForPage(mockPageable)).isEqualTo(5);
|
||||
assertThat(PagingUtils.getQueryResultSetLimitForPage(mockPageable)).isEqualTo(10);
|
||||
assertThat(PagingUtils.getQueryResultSetLimitForPage(mockPageable)).isEqualTo(15);
|
||||
|
||||
verify(mockPageable, times(3)).getPageNumber();
|
||||
verify(mockPageable, times(3)).getPageSize();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryResultSetLimitForPageWithNullPageableIsCorrect() {
|
||||
assertThat(PagingUtils.getQueryResultSetLimitForPage(null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizePageNumberFromPage() {
|
||||
|
||||
Page<?> mockPage = mock(Page.class);
|
||||
|
||||
doReturn(0).when(mockPage).getNumber();
|
||||
|
||||
assertThat(PagingUtils.normalizePageNumber(mockPage)).isOne();
|
||||
|
||||
verify(mockPage, times(1)).getNumber();
|
||||
verifyNoMoreInteractions(mockPage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizePageNumberFromNullPage() {
|
||||
assertThat(PagingUtils.normalizePageNumber((Page<?>) null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizePageNumberFromPageable() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
|
||||
assertThat(PagingUtils.normalizePageNumber(mockPageable)).isOne();
|
||||
|
||||
verify(mockPageable, times(1)).getPageNumber();
|
||||
verifyNoMoreInteractions(mockPageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizePageNumberFromNullPageable() {
|
||||
assertThat(PagingUtils.normalizePageNumber((Pageable) null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizesZeroIndexBasedPageNumbers() {
|
||||
|
||||
assertThat(PagingUtils.normalize(-2)).isEqualTo(0);
|
||||
assertThat(PagingUtils.normalize(-1)).isEqualTo(0);
|
||||
assertThat(PagingUtils.normalize(0)).isEqualTo(1);
|
||||
assertThat(PagingUtils.normalize(1)).isEqualTo(2);
|
||||
assertThat(PagingUtils.normalize(2)).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeSizeFromCollection() {
|
||||
|
||||
assertThat(PagingUtils.nullSafeSize(Collections.singleton(User.newUser("Jon Doe")))).isOne();
|
||||
assertThat(PagingUtils.nullSafeSize(Arrays.asList(User.newUser("Jon Doe"), User.newUser("Jane Doe"), User.newUser("Pie Doe")))).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeSizeFromEmptyCollection() {
|
||||
assertThat(PagingUtils.nullSafeSize(Collections.emptyList())).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeSizeFromIterable() {
|
||||
assertThat(PagingUtils.nullSafeSize(ArrayUtils.toIterable(User.newUser("Jon Doe"), User.newUser("Jane Doe")))).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeSizeFromEmptyIterable() {
|
||||
assertThat(PagingUtils.nullSafeSize(CollectionUtils.emptyIterable())).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeSizeFromNull() {
|
||||
assertThat(PagingUtils.nullSafeSize(null)).isZero();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Region("Users")
|
||||
@ToString(of = "name")
|
||||
@EqualsAndHashCode(of = "name")
|
||||
@RequiredArgsConstructor(staticName = "newUser")
|
||||
static class User {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@NonNull
|
||||
final String name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.gemfire.repository.query.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link TemplateBasedOqlQueryExecutor}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.query.support.TemplateBasedOqlQueryExecutor
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class TemplateBasedOqlQueryExecutorUnitTests {
|
||||
|
||||
@Test
|
||||
public void constructTemplateBasedOqlQueryExecutorSuccessfully() {
|
||||
|
||||
GemfireTemplate mockTemplate = mock(GemfireTemplate.class);
|
||||
|
||||
TemplateBasedOqlQueryExecutor queryExecutor = new TemplateBasedOqlQueryExecutor(mockTemplate);
|
||||
|
||||
assertThat(queryExecutor).isNotNull();
|
||||
assertThat(queryExecutor.getTemplate()).isEqualTo(mockTemplate);
|
||||
|
||||
verifyNoInteractions(mockTemplate);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructTemplateBasedOqlQueryExecutorWithNullTemplateThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new TemplateBasedOqlQueryExecutor(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("GemfireTemplate must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeCallsTemplateFind() {
|
||||
|
||||
GemfireTemplate mockTemplate = mock(GemfireTemplate.class);
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
SelectResults<?> mockSelectResults = mock(SelectResults.class);
|
||||
|
||||
String query = "SELECT * FROM /TestRegion WHERE id = $1";
|
||||
|
||||
doReturn(mockSelectResults).when(mockTemplate).find(eq(query), eq(1));
|
||||
|
||||
TemplateBasedOqlQueryExecutor queryExecutor = new TemplateBasedOqlQueryExecutor(mockTemplate);
|
||||
|
||||
assertThat(queryExecutor.execute(mockQueryMethod, query, 1)).isEqualTo(mockSelectResults);
|
||||
|
||||
verify(mockTemplate, times(1)).find(eq(query), eq(1));
|
||||
verifyNoInteractions(mockQueryMethod, mockSelectResults);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.gemfire.repository.sample;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
@@ -23,11 +24,12 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* The User class represents an authorized user of a service or computer system, etc.
|
||||
* Abstract Data Type (ADT) modeling an authorized user of an application, software service or computer system.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Comparable
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see org.springframework.data.gemfire.mapping.annotation.Region
|
||||
* @see Region
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@@ -45,11 +47,11 @@ public class User implements Comparable<User> {
|
||||
private final String username;
|
||||
|
||||
public User(String username) {
|
||||
Assert.hasText(username, "The username is required!");
|
||||
Assert.hasText(username, "Username is required");
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setActive(final Boolean active) {
|
||||
public void setActive(Boolean active) {
|
||||
this.active = Boolean.TRUE.equals(active);
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ public class User implements Comparable<User> {
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
return this.email;
|
||||
}
|
||||
|
||||
public void setSince(final Calendar since) {
|
||||
@@ -74,11 +76,11 @@ public class User implements Comparable<User> {
|
||||
}
|
||||
|
||||
public Calendar getSince() {
|
||||
return since;
|
||||
return this.since;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
return this.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +89,7 @@ public class User implements Comparable<User> {
|
||||
}
|
||||
|
||||
protected static boolean equalsIgnoreNull(Object obj1, Object obj2) {
|
||||
return obj1 == null ? obj2 == null : obj1.equals(obj2);
|
||||
return Objects.equals(obj1, obj2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -242,4 +242,40 @@ public class ArrayUtilsUnitTests {
|
||||
assertThat(sortedArray).isSameAs(array);
|
||||
assertThat(sortedArray).isEqualTo(new Comparable[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toIterableFromArray() {
|
||||
|
||||
Integer[] array = { 1, 2, 3 };
|
||||
|
||||
Iterable<Integer> iterable = ArrayUtils.toIterable(array);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
assertThat(iterable).hasSize(array.length);
|
||||
assertThat(iterable).containsExactly(array);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toIterableFromEmptyArray() {
|
||||
|
||||
Iterable<?> iterable = ArrayUtils.toIterable();
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
assertThat(iterable).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toIterableFromNullArrayThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
ArrayUtils.toIterable((Object[]) null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Array must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user