From 83057f12880f35c50475c691db791205c46bd0cb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 22 Sep 2017 16:53:31 +0200 Subject: [PATCH] DATAKV-197 - Introduce usage of nullable annotations for API validation. Annotate all packages with Spring Frameworks @NonNullApi and @NonNullFields. Add Spring's @Nullable to methods, parameters and fields that take or produce null values. Adapted using code to make sure the IDE can evaluate the null flow properly. Fix Javadoc in places where an invalid null handling policy was advertised. Strengthened null requirements for types that expose null-instances. Original Pull Request: #28 --- .../keyvalue/annotation/package-info.java | 6 +++ .../core/AbstractKeyValueAdapter.java | 5 +- .../data/keyvalue/core/CriteriaAccessor.java | 3 +- .../core/ForwardingCloseableIterator.java | 8 +-- .../data/keyvalue/core/IterableConverter.java | 5 +- .../data/keyvalue/core/KeyValueAdapter.java | 43 +++++++++------ .../data/keyvalue/core/KeyValueCallback.java | 6 ++- .../keyvalue/core/KeyValueOperations.java | 5 ++ ...eyValuePersistenceExceptionTranslator.java | 4 ++ .../data/keyvalue/core/KeyValueTemplate.java | 38 +++++++++----- .../data/keyvalue/core/QueryEngine.java | 52 +++++++++++++------ .../data/keyvalue/core/SortAccessor.java | 5 +- .../keyvalue/core/SpelPropertyComparator.java | 17 ++++-- .../data/keyvalue/core/SpelQueryEngine.java | 14 +++-- .../data/keyvalue/core/SpelSortAccessor.java | 2 +- .../core/UncategorizedKeyValueException.java | 12 ++++- .../keyvalue/core/event/KeyValueEvent.java | 30 ++++++----- .../keyvalue/core/event/package-info.java | 6 +++ .../AnnotationBasedKeySpaceResolver.java | 4 ++ .../BasicKeyValuePersistentEntity.java | 7 ++- .../core/mapping/KeySpaceResolver.java | 6 ++- .../context/KeyValueMappingContext.java | 3 +- .../core/mapping/context/package-info.java | 6 +++ .../keyvalue/core/mapping/package-info.java | 6 +++ .../data/keyvalue/core/package-info.java | 6 +++ .../keyvalue/core/query/KeyValueQuery.java | 25 +++++---- .../keyvalue/core/query/package-info.java | 6 +++ ...ValueRepositoryConfigurationExtension.java | 3 ++ .../repository/config/package-info.java | 6 +++ .../keyvalue/repository/package-info.java | 6 +++ .../query/CachingKeyValuePartTreeQuery.java | 4 +- .../query/KeyValuePartTreeQuery.java | 10 +++- .../repository/query/SpelQueryCreator.java | 2 +- .../repository/query/package-info.java | 6 +++ .../support/KeyValueQuerydslUtils.java | 10 ++-- .../support/KeyValueRepositoryFactory.java | 19 ++++--- .../KeyValueRepositoryFactoryBean.java | 7 +-- .../support/QuerydslKeyValueRepository.java | 11 ++-- .../repository/support/package-info.java | 6 +++ .../data/map/package-info.java | 6 +++ .../map/repository/config/package-info.java | 6 +++ .../core/IterableConverterUnitTests.java | 6 +-- .../KeyValueQuerydslUtilsUnitTests.java | 7 +-- .../QuerydslKeyValueRepositoryUnitTests.java | 15 ++++-- 44 files changed, 330 insertions(+), 130 deletions(-) create mode 100644 src/main/java/org/springframework/data/keyvalue/annotation/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/event/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/mapping/context/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/mapping/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/query/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/config/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/query/package-info.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/support/package-info.java create mode 100644 src/main/java/org/springframework/data/map/package-info.java create mode 100644 src/main/java/org/springframework/data/map/repository/config/package-info.java diff --git a/src/main/java/org/springframework/data/keyvalue/annotation/package-info.java b/src/main/java/org/springframework/data/keyvalue/annotation/package-info.java new file mode 100644 index 0000000..e4994ad --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/annotation/package-info.java @@ -0,0 +1,6 @@ +/** + * Key-Value annotations for declarative keyspace configuration. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.annotation; diff --git a/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java b/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java index 907d4a0..060b4f4 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java @@ -18,6 +18,7 @@ package org.springframework.data.keyvalue.core; import java.util.Collection; import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.lang.Nullable; /** * Base implementation of {@link KeyValueAdapter} holds {@link QueryEngine} to delegate {@literal find} and @@ -42,7 +43,7 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { * * @param engine will be defaulted to {@link SpelQueryEngine} if {@literal null}. */ - protected AbstractKeyValueAdapter(QueryEngine engine) { + protected AbstractKeyValueAdapter(@Nullable QueryEngine engine) { this.engine = engine != null ? engine : new SpelQueryEngine(); this.engine.registerAdapter(this); @@ -61,6 +62,7 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueAdapter#get(java.lang.Object, java.lang.String, java.lang.Class) */ + @Nullable @Override public T get(Object id, String keyspace, Class type) { return type.cast(get(id, keyspace)); @@ -70,6 +72,7 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueAdapter#delete(java.lang.Object, java.lang.String, java.lang.Class) */ + @Nullable @Override public T delete(Object id, String keyspace, Class type) { return type.cast(delete(id, keyspace)); diff --git a/src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java b/src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java index 810cba3..b29cfd5 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java +++ b/src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java @@ -21,6 +21,7 @@ import org.springframework.data.keyvalue.core.query.KeyValueQuery; * Resolves the criteria object from given {@link KeyValueQuery}. * * @author Christoph Strobl + * @author Mark Paluch * @param */ public interface CriteriaAccessor { @@ -29,7 +30,7 @@ public interface CriteriaAccessor { * Checks and reads {@link KeyValueQuery#getCriteria()} of given {@link KeyValueQuery}. Might also apply additional * transformation to match the desired type. * - * @param query can be {@literal null}. + * @param query must not be {@literal null}. * @return the criteria extracted from the query. * @throws IllegalArgumentException in case the criteria is not valid for usage with specific * {@link CriteriaAccessor}. diff --git a/src/main/java/org/springframework/data/keyvalue/core/ForwardingCloseableIterator.java b/src/main/java/org/springframework/data/keyvalue/core/ForwardingCloseableIterator.java index 0ecc15d..981d993 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/ForwardingCloseableIterator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/ForwardingCloseableIterator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.data.keyvalue.core; import java.util.Iterator; import org.springframework.data.util.CloseableIterator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -26,13 +27,14 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch * @param * @param */ public class ForwardingCloseableIterator implements CloseableIterator { private final Iterator delegate; - private final Runnable closeHandler; + private final @Nullable Runnable closeHandler; /** * Creates a new {@link ForwardingCloseableIterator}. @@ -50,7 +52,7 @@ public class ForwardingCloseableIterator implements CloseableIterator { * @param delegate must not be {@literal null}. * @param closeHandler may be {@literal null}. */ - public ForwardingCloseableIterator(Iterator delegate, Runnable closeHandler) { + public ForwardingCloseableIterator(Iterator delegate, @Nullable Runnable closeHandler) { Assert.notNull(delegate, "Delegate iterator must not be null!"); diff --git a/src/main/java/org/springframework/data/keyvalue/core/IterableConverter.java b/src/main/java/org/springframework/data/keyvalue/core/IterableConverter.java index cb2ee81..4bb7ae8 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/IterableConverter.java +++ b/src/main/java/org/springframework/data/keyvalue/core/IterableConverter.java @@ -24,6 +24,7 @@ import java.util.List; * Converter capable of transforming a given {@link Iterable} into a collection type. * * @author Christoph Strobl + * @author Mark Paluch */ public final class IterableConverter { @@ -37,10 +38,6 @@ public final class IterableConverter { */ public static List toList(Iterable source) { - if (source == null) { - return Collections.emptyList(); - } - if (source instanceof List) { return (List) source; } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java index 15c906e..ca6f805 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java @@ -21,12 +21,14 @@ import java.util.Map; import org.springframework.beans.factory.DisposableBean; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.util.CloseableIterator; +import org.springframework.lang.Nullable; /** * {@link KeyValueAdapter} unifies access and shields the underlying key/value specific implementation. * * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public interface KeyValueAdapter extends DisposableBean { @@ -55,33 +57,41 @@ public interface KeyValueAdapter extends DisposableBean { * @param keyspace must not be {@literal null}. * @return {@literal null} in case no matching item exists. */ + @Nullable Object get(Object id, String keyspace); /** - * @param id - * @param keyspace - * @param type - * @return + * Get the object with given id from keyspace. + * + * @param id must not be {@literal null}. + * @param keyspace must not be {@literal null}. + * @param type must not be {@literal null}. + * @return {@literal null} in case no matching item exists. * @since 1.1 */ + @Nullable T get(Object id, String keyspace, Class type); /** - * Delete and return the obect with given type and id. + * Delete and return the object with given type and id. * * @param id must not be {@literal null}. * @param keyspace must not be {@literal null}. * @return {@literal null} if object could not be found */ + @Nullable Object delete(Object id, String keyspace); /** - * @param id - * @param keyspace - * @param type - * @return + * Delete and return the object with given type and id. + * + * @param id must not be {@literal null}. + * @param keyspace must not be {@literal null}. + * @param type must not be {@literal null}. + * @return {@literal null} if object could not be found * @since 1.1 */ + @Nullable T delete(Object id, String keyspace, Class type); /** @@ -95,7 +105,7 @@ public interface KeyValueAdapter extends DisposableBean { /** * Returns a {@link CloseableIterator} that iterates over all entries. * - * @param keyspace + * @param keyspace must not be {@literal null}. * @return */ CloseableIterator> entries(String keyspace); @@ -115,17 +125,17 @@ public interface KeyValueAdapter extends DisposableBean { /** * Find all matching objects within {@literal keyspace}. * - * @param query + * @param query must not be {@literal null}. * @param keyspace must not be {@literal null}. * @return empty {@link Collection} if no match found. */ Iterable find(KeyValueQuery query, String keyspace); /** - * @param query - * @param keyspace - * @param type - * @return + * @param query must not be {@literal null}. + * @param keyspace must not be {@literal null}. + * @param type must not be {@literal null}. + * @return empty {@link Collection} if no match found. * @since 1.1 */ Iterable find(KeyValueQuery query, String keyspace, Class type); @@ -134,13 +144,14 @@ public interface KeyValueAdapter extends DisposableBean { * Count number of objects within {@literal keyspace}. * * @param keyspace must not be {@literal null}. + * @return */ long count(String keyspace); /** * Count all matching objects within {@literal keyspace}. * - * @param query + * @param query must not be {@literal null}. * @param keyspace must not be {@literal null}. * @return */ diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java index 93f85da..b54c969 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,14 @@ */ package org.springframework.data.keyvalue.core; +import org.springframework.lang.Nullable; + /** * Generic callback interface for code that operates on a {@link KeyValueAdapter}. This is particularly useful for * delegating code that needs to work closely on the underlying key/value store implementation. * * @author Christoph Strobl + * @author Mark Paluch * @param */ public interface KeyValueCallback { @@ -31,5 +34,6 @@ public interface KeyValueCallback { * @param adapter * @return */ + @Nullable T doInKeyValue(KeyValueAdapter adapter); } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java index 98b7675..d534a36 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java @@ -22,11 +22,13 @@ import org.springframework.data.domain.Sort; import org.springframework.data.keyvalue.annotation.KeySpace; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; /** * Interface that specifies a basic set of key/value operations. Implemented by {@link KeyValueTemplate}. * * @author Christoph Strobl + * @author Mark Paluch */ public interface KeyValueOperations extends DisposableBean { @@ -81,6 +83,7 @@ public interface KeyValueOperations extends DisposableBean { * @param action must not be {@literal null}. * @return */ + @Nullable T execute(KeyValueCallback action); /** @@ -139,6 +142,7 @@ public interface KeyValueOperations extends DisposableBean { * @param objectToDelete must not be {@literal null}. * @return */ + @Nullable T delete(T objectToDelete); /** @@ -148,6 +152,7 @@ public interface KeyValueOperations extends DisposableBean { * @param type must not be {@literal null}. * @return the deleted item or {@literal null} if no match found. */ + @Nullable T delete(Object id, Class type); /** diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslator.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslator.java index 302631e..7341764 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslator.java @@ -20,6 +20,7 @@ import java.util.NoSuchElementException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -27,6 +28,7 @@ import org.springframework.util.Assert; * exception to an appropriate exception from the {@code org.springframework.dao} hierarchy. * * @author Christoph Strobl + * @author Mark Paluch */ public class KeyValuePersistenceExceptionTranslator implements PersistenceExceptionTranslator { @@ -34,6 +36,7 @@ public class KeyValuePersistenceExceptionTranslator implements PersistenceExcept * (non-Javadoc) * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) */ + @Nullable @Override public DataAccessException translateExceptionIfPossible(RuntimeException exception) { @@ -51,6 +54,7 @@ public class KeyValuePersistenceExceptionTranslator implements PersistenceExcept if (exception.getClass().getName().startsWith("java")) { return new UncategorizedKeyValueException(exception.getMessage(), exception); } + return null; } } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java index cef7703..85755ac 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java @@ -34,6 +34,7 @@ import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -55,7 +56,7 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub private final IdentifierGenerator identifierGenerator; private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR; - private ApplicationEventPublisher eventPublisher; + private @Nullable ApplicationEventPublisher eventPublisher; private boolean publishEvents = true; private @SuppressWarnings("rawtypes") Set> eventTypesToPublish = Collections .emptySet(); @@ -215,14 +216,10 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub Assert.notNull(type, "Type to fetch must not be null!"); - return execute(adapter -> { + return executeRequired(adapter -> { Iterable values = adapter.getAllOf(resolveKeySpace(type)); - if (values == null) { - return Collections.emptySet(); - } - ArrayList filtered = new ArrayList<>(); for (Object candidate : values) { if (typeCheck(type, candidate)) { @@ -336,6 +333,7 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#execute(org.springframework.data.keyvalue.core.KeyValueCallback) */ + @Nullable @Override public T execute(KeyValueCallback action) { @@ -348,6 +346,24 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub } } + /** + * Execute {@link KeyValueCallback} and require a non-{@literal null} return value. + * + * @param action + * @param + * @return + */ + protected T executeRequired(KeyValueCallback action) { + + T result = execute(action); + + if (result != null) { + return result; + } + + throw new IllegalStateException(String.format("KeyValueCallback %s returned null value!", action)); + } + /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class) @@ -355,12 +371,9 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub @Override public Iterable find(KeyValueQuery query, Class type) { - return execute((KeyValueCallback>) adapter -> { + return executeRequired((KeyValueCallback>) adapter -> { Iterable result = adapter.find(query, resolveKeySpace(type), type); - if (result == null) { - return Collections.emptySet(); - } List filtered = new ArrayList<>(); @@ -410,8 +423,7 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub */ @Override public long count(KeyValueQuery query, Class type) { - - return execute(adapter -> adapter.count(query, resolveKeySpace(type))); + return executeRequired(adapter -> adapter.count(query, resolveKeySpace(type))); } /* @@ -460,7 +472,7 @@ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPub } } - private static boolean typeCheck(Class requiredType, Object candidate) { + private static boolean typeCheck(Class requiredType, @Nullable Object candidate) { return candidate == null || ClassUtils.isAssignable(requiredType, candidate.getClass()); } } diff --git a/src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java b/src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java index 0477608..b1d212b 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java +++ b/src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java @@ -16,28 +16,31 @@ package org.springframework.data.keyvalue.core; import java.util.Collection; +import java.util.Optional; import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.lang.Nullable; /** * Base implementation for accessing and executing {@link KeyValueQuery} against a {@link KeyValueAdapter}. * * @author Christoph Strobl + * @author Mark Paluch * @param * @param * @param */ public abstract class QueryEngine { - private final CriteriaAccessor criteriaAccessor; - private final SortAccessor sortAccessor; + private final Optional> criteriaAccessor; + private final Optional> sortAccessor; - private ADAPTER adapter; + private @Nullable ADAPTER adapter; - public QueryEngine(CriteriaAccessor criteriaAccessor, SortAccessor sortAccessor) { + public QueryEngine(@Nullable CriteriaAccessor criteriaAccessor, @Nullable SortAccessor sortAccessor) { - this.criteriaAccessor = criteriaAccessor; - this.sortAccessor = sortAccessor; + this.criteriaAccessor = Optional.ofNullable(criteriaAccessor); + this.sortAccessor = Optional.ofNullable(sortAccessor); } /** @@ -49,8 +52,8 @@ public abstract class QueryEngine execute(KeyValueQuery query, String keyspace) { - CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null; - SORT sort = this.sortAccessor != null ? this.sortAccessor.resolve(query) : null; + CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); + SORT sort = this.sortAccessor.map(it -> it.resolve(query)).orElse(null); return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace); } @@ -64,8 +67,8 @@ public abstract class QueryEngine Collection execute(KeyValueQuery query, String keyspace, Class type) { - CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null; - SORT sort = this.sortAccessor != null ? this.sortAccessor.resolve(query) : null; + CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); + SORT sort = this.sortAccessor.map(it -> it.resolve(query)).orElse(null); return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace, type); } @@ -79,7 +82,7 @@ public abstract class QueryEngine query, String keyspace) { - CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null; + CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); return count(criteria, keyspace); } @@ -91,7 +94,8 @@ public abstract class QueryEngine execute(CRITERIA criteria, SORT sort, long offset, int rows, String keyspace); + public abstract Collection execute(@Nullable CRITERIA criteria, @Nullable SORT sort, long offset, int rows, + String keyspace); /** * @param criteria @@ -104,8 +108,8 @@ public abstract class QueryEngine Collection execute(CRITERIA criteria, SORT sort, long offset, int rows, String keyspace, - Class type) { + public Collection execute(@Nullable CRITERIA criteria, @Nullable SORT sort, long offset, int rows, + String keyspace, Class type) { return (Collection) execute(criteria, sort, offset, rows, keyspace); } @@ -114,17 +118,35 @@ public abstract class QueryEngine */ public interface SortAccessor { @@ -31,7 +32,7 @@ public interface SortAccessor { * Reads {@link KeyValueQuery#getSort()} of given {@link KeyValueQuery} and applies required transformation to match * the desired type. * - * @param query can be {@literal null}. + * @param query must not be {@literal null}. * @return {@literal null} in case {@link Sort} has not been defined on {@link KeyValueQuery}. */ T resolve(KeyValueQuery query); diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java b/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java index f5fe2ac..2d6c258 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java @@ -19,12 +19,15 @@ import java.util.Comparator; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * {@link Comparator} implementation using {@link SpelExpression}. * * @author Christoph Strobl * @author Oliver Gierke + * @author Mark Paluch * @param */ public class SpelPropertyComparator implements Comparator { @@ -34,16 +37,19 @@ public class SpelPropertyComparator implements Comparator { private boolean asc = true; private boolean nullsFirst = true; - private SpelExpression expression; + private @Nullable SpelExpression expression; /** - * Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}.. + * Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}. * * @param path must not be {@literal null} or empty. * @param parser must not be {@literal null}. */ public SpelPropertyComparator(String path, SpelExpressionParser parser) { + Assert.hasText(path, "Path must not be null or empty!"); + Assert.notNull(parser, "SpelExpressionParser must not be null!"); + this.path = path; this.parser = parser; } @@ -109,9 +115,9 @@ public class SpelPropertyComparator implements Comparator { */ protected String buildExpressionForPath() { - String rawExpression = "new org.springframework.util.comparator.NullSafeComparator(new org.springframework.util.comparator.ComparableComparator(), " - + Boolean.toString(this.nullsFirst) + ").compare(" + "#arg1?." + (path != null ? path.replace(".", "?.") : "") - + "," + "#arg2?." + (path != null ? path.replace(".", "?.") : "") + ")"; + String rawExpression = String.format( + "new org.springframework.util.comparator.NullSafeComparator(new org.springframework.util.comparator.ComparableComparator(), %s).compare(#arg1?.%s,#arg2?.%s)", + Boolean.toString(this.nullsFirst), path.replace(".", "?."), path.replace(".", "?.")); return rawExpression; } @@ -120,6 +126,7 @@ public class SpelPropertyComparator implements Comparator { * (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ + @SuppressWarnings("ConstantConditions") @Override public int compare(T arg1, T arg2) { diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java b/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java index d23c3d2..316057c 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java @@ -25,6 +25,7 @@ import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; /** * {@link QueryEngine} implementation specific for executing {@link SpelExpression} based {@link KeyValueQuery} against @@ -52,7 +53,7 @@ class SpelQueryEngine extends QueryEngine execute(SpelCriteria criteria, Comparator sort, long offset, int rows, String keyspace) { - return sortAndFilterMatchingRange(getAdapter().getAllOf(keyspace), criteria, sort, offset, rows); + return sortAndFilterMatchingRange(getRequiredAdapter().getAllOf(keyspace), criteria, sort, offset, rows); } /* @@ -61,12 +62,13 @@ class SpelQueryEngine extends QueryEngine sortAndFilterMatchingRange(Iterable source, SpelCriteria criteria, Comparator sort, long offset, - int rows) { + private List sortAndFilterMatchingRange(Iterable source, SpelCriteria criteria, @Nullable Comparator sort, + long offset, int rows) { List tmp = IterableConverter.toList(source); if (sort != null) { @@ -76,7 +78,8 @@ class SpelQueryEngine extends QueryEngine List filterMatchingRange(List source, SpelCriteria criteria, long offset, int rows) { + private static List filterMatchingRange(List source, @Nullable SpelCriteria criteria, long offset, + int rows) { Stream stream = source.stream(); @@ -93,6 +96,7 @@ class SpelQueryEngine extends QueryEngine> { @Override public Comparator resolve(KeyValueQuery query) { - if (query == null || query.getSort() == null || query.getSort().isUnsorted()) { + if (query.getSort().isUnsorted()) { return null; } diff --git a/src/main/java/org/springframework/data/keyvalue/core/UncategorizedKeyValueException.java b/src/main/java/org/springframework/data/keyvalue/core/UncategorizedKeyValueException.java index 07f371f..05f63e7 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/UncategorizedKeyValueException.java +++ b/src/main/java/org/springframework/data/keyvalue/core/UncategorizedKeyValueException.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,22 @@ package org.springframework.data.keyvalue.core; import org.springframework.dao.UncategorizedDataAccessException; /** + * Normal superclass when we can't distinguish anything more specific than "something went wrong with the underlying + * resource". + * * @author Christoph Strobl + * @author Mark Paluch */ public class UncategorizedKeyValueException extends UncategorizedDataAccessException { private static final long serialVersionUID = -8087116071859122297L; + /** + * Creates a new {@link UncategorizedKeyValueException}. + * + * @param msg the detail message. + * @param cause the root cause (usually from using a underlying data access API). + */ public UncategorizedKeyValueException(String msg, Throwable cause) { super(msg, cause); } diff --git a/src/main/java/org/springframework/data/keyvalue/core/event/KeyValueEvent.java b/src/main/java/org/springframework/data/keyvalue/core/event/KeyValueEvent.java index 4377214..236116e 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/event/KeyValueEvent.java +++ b/src/main/java/org/springframework/data/keyvalue/core/event/KeyValueEvent.java @@ -16,6 +16,7 @@ package org.springframework.data.keyvalue.core.event; import org.springframework.context.ApplicationEvent; +import org.springframework.lang.Nullable; /** * {@link KeyValueEvent} gets published for operations executed by eg. @@ -24,6 +25,7 @@ import org.springframework.context.ApplicationEvent; * * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch * @param */ public class KeyValueEvent extends ApplicationEvent { @@ -71,7 +73,7 @@ public class KeyValueEvent extends ApplicationEvent { * @param value * @return */ - public static AfterGetEvent afterGet(Object id, String keyspace, Class type, T value) { + public static AfterGetEvent afterGet(Object id, String keyspace, Class type, @Nullable T value) { return new AfterGetEvent<>(id, keyspace, type, value); } @@ -125,7 +127,7 @@ public class KeyValueEvent extends ApplicationEvent { * @return */ public static AfterUpdateEvent afterUpdate(Object id, String keyspace, Class type, T actualValue, - Object previousValue) { + @Nullable Object previousValue) { return new AfterUpdateEvent<>(id, keyspace, type, actualValue, previousValue); } @@ -172,7 +174,8 @@ public class KeyValueEvent extends ApplicationEvent { * @param value * @return */ - public static AfterDeleteEvent afterDelete(Object id, String keyspace, Class type, T value) { + public static AfterDeleteEvent afterDelete(Object id, String keyspace, Class type, + @Nullable T value) { return new AfterDeleteEvent<>(id, keyspace, type, value); } @@ -223,9 +226,9 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") abstract static class KeyBasedEventWithPayload extends KeyBasedEvent { - private final T payload; + private final @Nullable T payload; - KeyBasedEventWithPayload(Object key, String keyspace, Class type, T payload) { + KeyBasedEventWithPayload(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type); this.payload = payload; } @@ -235,6 +238,7 @@ public class KeyValueEvent extends ApplicationEvent { * * @return */ + @Nullable public T getPayload() { return payload; } @@ -264,7 +268,7 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class AfterGetEvent extends KeyBasedEventWithPayload { - protected AfterGetEvent(Object key, String keyspace, Class type, T payload) { + protected AfterGetEvent(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type, payload); } @@ -279,7 +283,7 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class BeforeInsertEvent extends KeyBasedEventWithPayload { - public BeforeInsertEvent(Object key, String keyspace, Class type, T payload) { + public BeforeInsertEvent(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type, payload); } @@ -294,7 +298,7 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class AfterInsertEvent extends KeyBasedEventWithPayload { - public AfterInsertEvent(Object key, String keyspace, Class type, T payload) { + public AfterInsertEvent(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type, payload); } } @@ -308,7 +312,7 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class BeforeUpdateEvent extends KeyBasedEventWithPayload { - public BeforeUpdateEvent(Object key, String keyspace, Class type, T payload) { + public BeforeUpdateEvent(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type, payload); } } @@ -322,9 +326,10 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class AfterUpdateEvent extends KeyBasedEventWithPayload { - private final Object existing; + private final @Nullable Object existing; - public AfterUpdateEvent(Object key, String keyspace, Class type, T payload, Object existing) { + public AfterUpdateEvent(Object key, String keyspace, Class type, T payload, + @Nullable Object existing) { super(key, keyspace, type, payload); this.existing = existing; } @@ -334,6 +339,7 @@ public class KeyValueEvent extends ApplicationEvent { * * @return */ + @Nullable public Object before() { return existing; } @@ -371,7 +377,7 @@ public class KeyValueEvent extends ApplicationEvent { @SuppressWarnings("serial") public static class AfterDeleteEvent extends KeyBasedEventWithPayload { - public AfterDeleteEvent(Object key, String keyspace, Class type, T payload) { + public AfterDeleteEvent(Object key, String keyspace, Class type, @Nullable T payload) { super(key, keyspace, type, payload); } } diff --git a/src/main/java/org/springframework/data/keyvalue/core/event/package-info.java b/src/main/java/org/springframework/data/keyvalue/core/event/package-info.java new file mode 100644 index 0000000..7ec69d8 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/event/package-info.java @@ -0,0 +1,6 @@ +/** + * Support classes for key-value events, like standard persistence lifecycle events. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.core.event; diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolver.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolver.java index 6561b65..1dc9ea5 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolver.java +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolver.java @@ -19,6 +19,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.Persistent; import org.springframework.data.keyvalue.annotation.KeySpace; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -28,6 +29,7 @@ import org.springframework.util.ClassUtils; * * @author Christoph Strobl * @author Oliver Gierke + * @author Mark Paluch */ enum AnnotationBasedKeySpaceResolver implements KeySpaceResolver { @@ -38,6 +40,7 @@ enum AnnotationBasedKeySpaceResolver implements KeySpaceResolver { * @see org.springframework.data.keyvalue.core.KeySpaceResolver#resolveKeySpace(java.lang.Class) */ @Override + @Nullable public String resolveKeySpace(Class type) { Assert.notNull(type, "Type for keyspace for null!"); @@ -48,6 +51,7 @@ enum AnnotationBasedKeySpaceResolver implements KeySpaceResolver { return keySpace != null ? keySpace.toString() : null; } + @Nullable private static Object getKeySpace(Class type) { KeySpace keyspace = AnnotatedElementUtils.findMergedAnnotation(type, KeySpace.class); diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntity.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntity.java index 5629d19..39aef43 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntity.java +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntity.java @@ -17,6 +17,7 @@ package org.springframework.data.keyvalue.core.mapping; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -24,6 +25,7 @@ import org.springframework.util.StringUtils; * * @author Christoph Strobl * @author Oliver Gierke + * @author Mark Paluch * @param */ public class BasicKeyValuePersistentEntity> @@ -37,14 +39,15 @@ public class BasicKeyValuePersistentEntity information, KeySpaceResolver fallbackKeySpaceResolver) { + public BasicKeyValuePersistentEntity(TypeInformation information, + @Nullable KeySpaceResolver fallbackKeySpaceResolver) { super(information); this.keyspace = detectKeySpace(information.getType(), fallbackKeySpaceResolver); } - private static String detectKeySpace(Class type, KeySpaceResolver fallback) { + private static String detectKeySpace(Class type, @Nullable KeySpaceResolver fallback) { String keySpace = AnnotationBasedKeySpaceResolver.INSTANCE.resolveKeySpace(type); diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/KeySpaceResolver.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/KeySpaceResolver.java index dae56ec..9f18852 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/KeySpaceResolver.java +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/KeySpaceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,14 @@ */ package org.springframework.data.keyvalue.core.mapping; +import org.springframework.lang.Nullable; + /** * {@link KeySpaceResolver} determines the {@literal keyspace} a given type is assigned to. A keyspace in this context * is a specific region/collection/grouping of elements sharing a common keyrange.
* * @author Christoph Strobl + * @author Mark Paluch */ public interface KeySpaceResolver { @@ -29,5 +32,6 @@ public interface KeySpaceResolver { * @param type must not be {@literal null}. * @return */ + @Nullable String resolveKeySpace(Class type); } diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java index 1eda7f3..f076c23 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java @@ -24,6 +24,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * Default implementation of a {@link MappingContext} using {@link KeyValuePersistentEntity} and @@ -36,7 +37,7 @@ import org.springframework.data.util.TypeInformation; public class KeyValueMappingContext, P extends KeyValuePersistentProperty

> extends AbstractMappingContext { - private KeySpaceResolver fallbackKeySpaceResolver; + private @Nullable KeySpaceResolver fallbackKeySpaceResolver; /** * Configures the {@link KeySpaceResolver} to be used if not explicit key space is annotated to the domain type. diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/context/package-info.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/package-info.java new file mode 100644 index 0000000..fa07bdc --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/package-info.java @@ -0,0 +1,6 @@ +/** + * Infrastructure for the Key-Value mapping context. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.core.mapping.context; diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/package-info.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/package-info.java new file mode 100644 index 0000000..aa504fc --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/package-info.java @@ -0,0 +1,6 @@ +/** + * Infrastructure for the Key-Value mapping subsystem and keyspace resolution. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.core.mapping; diff --git a/src/main/java/org/springframework/data/keyvalue/core/package-info.java b/src/main/java/org/springframework/data/keyvalue/core/package-info.java new file mode 100644 index 0000000..e246194 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/package-info.java @@ -0,0 +1,6 @@ +/** + * Core key/value implementation. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.core; diff --git a/src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java b/src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java index 5a974e0..d893b21 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java +++ b/src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java @@ -16,6 +16,8 @@ package org.springframework.data.keyvalue.core.query; import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * @author Christoph Strobl @@ -27,7 +29,7 @@ public class KeyValueQuery { private Sort sort = Sort.unsorted(); private long offset = -1; private int rows = -1; - private T criteria; + private @Nullable T criteria; /** * Creates new instance of {@link KeyValueQuery}. @@ -39,17 +41,17 @@ public class KeyValueQuery { * * @param criteria can be {@literal null}. */ - public KeyValueQuery(T criteria) { + public KeyValueQuery(@Nullable T criteria) { this.criteria = criteria; } /** * Creates new instance of {@link KeyValueQuery} with given {@link Sort}. * - * @param sort can be {@literal null}. + * @param sort must not be {@literal null}. */ public KeyValueQuery(Sort sort) { - this.sort = sort; + setSort(sort); } /** @@ -58,6 +60,7 @@ public class KeyValueQuery { * @return * @since 2.0 */ + @Nullable public T getCriteria() { return criteria; } @@ -113,26 +116,28 @@ public class KeyValueQuery { * @param sort */ public void setSort(Sort sort) { + + Assert.notNull(sort, "Sort must not be null!"); + this.sort = sort; } /** * Add given {@link Sort}. * - * @param sort {@literal null} {@link Sort} will be ignored. + * @param sort must not be {@literal null}. * @return */ public KeyValueQuery orderBy(Sort sort) { - if (sort == null) { - return this; - } + Assert.notNull(sort, "Sort must not be null!"); - if (this.sort != null) { + if (this.sort.isSorted()) { this.sort = this.sort.and(sort); } else { this.sort = sort; } + return this; } @@ -142,7 +147,9 @@ public class KeyValueQuery { * @return */ public KeyValueQuery skip(long offset) { + setOffset(offset); + return this; } diff --git a/src/main/java/org/springframework/data/keyvalue/core/query/package-info.java b/src/main/java/org/springframework/data/keyvalue/core/query/package-info.java new file mode 100644 index 0000000..b47343b --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/query/package-info.java @@ -0,0 +1,6 @@ +/** + * Key/value specific query and abstractions. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.core.query; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java index d1658a4..99088f2 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java @@ -35,6 +35,7 @@ import org.springframework.data.repository.config.AnnotationRepositoryConfigurat import org.springframework.data.repository.config.RepositoryConfigurationExtension; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.RepositoryConfigurationSource; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; /** @@ -42,6 +43,7 @@ import org.springframework.util.CollectionUtils; * * @author Christoph Strobl * @author Oliver Gierke + * @author Mark Paluch */ public abstract class KeyValueRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { @@ -177,6 +179,7 @@ public abstract class KeyValueRepositoryConfigurationExtension extends Repositor * * @return {@literal null} to explicitly not register a template. */ + @Nullable protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition( RepositoryConfigurationSource configurationSource) { return null; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/package-info.java b/src/main/java/org/springframework/data/keyvalue/repository/config/package-info.java new file mode 100644 index 0000000..8257cd5 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/package-info.java @@ -0,0 +1,6 @@ +/** + * Support infrastructure for the configuration of key/value specific repositories. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.repository.config; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/package-info.java b/src/main/java/org/springframework/data/keyvalue/repository/package-info.java new file mode 100644 index 0000000..8212e37 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/package-info.java @@ -0,0 +1,6 @@ +/** + * Key/value specific repository implementation. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.repository; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQuery.java b/src/main/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQuery.java index 66fd098..aea9ee2 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQuery.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQuery.java @@ -21,17 +21,19 @@ import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.lang.Nullable; /** * {@link KeyValuePartTreeQuery} implementation deriving queries from {@link PartTree} using a predefined * {@link AbstractQueryCreator} that caches the once created query. * * @author Christoph Strobl + * @author Mark Paluch * @since 1.1 */ public class CachingKeyValuePartTreeQuery extends KeyValuePartTreeQuery { - private KeyValueQuery cachedQuery; + private @Nullable KeyValueQuery cachedQuery; public CachingKeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evaluationContextProvider, KeyValueOperations keyValueOperations, Class> queryCreator) { diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java b/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java index dbd3504..a52f89f 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java @@ -35,6 +35,7 @@ import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -94,6 +95,7 @@ public class KeyValuePartTreeQuery implements RepositoryQuery { * @param parameters * @param query */ + @Nullable @SuppressWarnings({ "unchecked", "rawtypes" }) protected Object doExecute(Object[] parameters, KeyValueQuery query) { @@ -179,8 +181,12 @@ public class KeyValuePartTreeQuery implements RepositoryQuery { PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType()); - Constructor> constructor = ClassUtils - .getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class); + Constructor> constructor = ClassUtils.getConstructorIfAvailable(queryCreator, + PartTree.class, ParameterAccessor.class); + + Assert.state(constructor != null, String.format("Constructor %s(PartTree, ParameterAccessor) not available!", + ClassUtils.getShortName(queryCreator))); + KeyValueQuery query = (KeyValueQuery) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery(); if (tree.isLimiting()) { diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java index 28bb9af..a004860 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java @@ -89,7 +89,7 @@ public class SpelQueryCreator extends AbstractQueryCreator query = new KeyValueQuery<>(this.expression); - if (sort != null) { + if (sort.isSorted()) { query.orderBy(sort); } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/package-info.java b/src/main/java/org/springframework/data/keyvalue/repository/query/package-info.java new file mode 100644 index 0000000..64ad179 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/package-info.java @@ -0,0 +1,6 @@ +/** + * Query derivation mechanism for key/value specific repositories providing a generic SpEL based implementation. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.repository.query; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtils.java b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtils.java index 2e574e1..e1bc644 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtils.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtils.java @@ -37,6 +37,7 @@ import com.querydsl.core.types.dsl.PathBuilder; * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch */ abstract class KeyValueQuerydslUtils { @@ -47,17 +48,14 @@ abstract class KeyValueQuerydslUtils { /** * Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}. * - * @param sort + * @param sort must not be {@literal null}. * @param builder must not be {@literal null}. * @return empty {@code OrderSpecifier[]} when sort is {@literal null}. */ static OrderSpecifier[] toOrderSpecifier(Sort sort, PathBuilder builder) { - Assert.notNull(builder, "Builder must not be 'null'."); - - if (sort == null) { - return new OrderSpecifier[0]; - } + Assert.notNull(sort, "Sort must not be null."); + Assert.notNull(builder, "Builder must not be null."); List> specifiers = null; diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java index 2df83f6..82caaf5 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java @@ -15,7 +15,7 @@ */ package org.springframework.data.keyvalue.repository.support; -import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT; +import static org.springframework.data.querydsl.QuerydslUtils.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -41,6 +41,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -75,7 +76,7 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { * {@link AbstractQueryCreator}-type. * * @param keyValueOperations must not be {@literal null}. - * @param queryCreator defaulted to {@link #DEFAULT_QUERY_CREATOR} if {@literal null}. + * @param queryCreator must not be {@literal null}. */ public KeyValueRepositoryFactory(KeyValueOperations keyValueOperations, Class> queryCreator) { @@ -154,9 +155,10 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider) */ @Override - protected Optional getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) { - return Optional.of(new KeyValueQueryLookupStrategy(key, evaluationContextProvider, this.keyValueOperations, this.queryCreator, - this.repositoryQueryType)); + protected Optional getQueryLookupStrategy(@Nullable Key key, + EvaluationContextProvider evaluationContextProvider) { + return Optional.of(new KeyValueQueryLookupStrategy(key, evaluationContextProvider, this.keyValueOperations, + this.queryCreator, this.repositoryQueryType)); } /** @@ -194,7 +196,7 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { * @param queryCreator * @since 1.1 */ - public KeyValueQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider, + public KeyValueQueryLookupStrategy(@Nullable Key key, EvaluationContextProvider evaluationContextProvider, KeyValueOperations keyValueOperations, Class> queryCreator, Class repositoryQueryType) { @@ -224,6 +226,11 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { .getConstructorIfAvailable(this.repositoryQueryType, QueryMethod.class, EvaluationContextProvider.class, KeyValueOperations.class, Class.class); + Assert.state(constructor != null, + String.format( + "Constructor %s(QueryMethod, EvaluationContextProvider, KeyValueOperations, Class) not available!", + ClassUtils.getShortName(this.repositoryQueryType))); + return BeanUtils.instantiateClass(constructor, queryMethod, evaluationContextProvider, this.keyValueOperations, this.queryCreator); } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java index 705dc03..231e1ba 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java @@ -24,6 +24,7 @@ import org.springframework.data.repository.core.support.RepositoryFactoryBeanSup import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -36,9 +37,9 @@ import org.springframework.util.Assert; public class KeyValueRepositoryFactoryBean, S, ID> extends RepositoryFactoryBeanSupport { - private KeyValueOperations operations; - private Class> queryCreator; - private Class repositoryQueryType; + private @Nullable KeyValueOperations operations; + private @Nullable Class> queryCreator; + private @Nullable Class repositoryQueryType; /** * Creates a new {@link KeyValueRepositoryFactoryBean} for the given repository interface. diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java index 84b242a..989c790 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java @@ -30,6 +30,7 @@ import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -47,6 +48,7 @@ import com.querydsl.core.types.dsl.PathBuilder; * @author Christoph Strobl * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch * @param the domain type to manage * @param the identifier type of the domain type */ @@ -55,7 +57,6 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; - private final EntityPath path; private final PathBuilder builder; /** @@ -84,7 +85,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< Assert.notNull(resolver, "EntityPathResolver must not be null!"); - this.path = resolver.createPath(entityInformation.getJavaType()); + EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.builder = new PathBuilder<>(path.getType(), path.getMetadata()); } @@ -142,12 +143,12 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< AbstractCollQuery query = prepareQuery(predicate); - if (pageable != null) { + if (pageable.isPaged() || pageable.getSort().isSorted()) { query.offset(pageable.getOffset()); query.limit(pageable.getPageSize()); - if (pageable.getSort() != null) { + if (pageable.getSort().isSorted()) { query.orderBy(toOrderSpecifier(pageable.getSort(), builder)); } } @@ -196,7 +197,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< * @param predicate * @return */ - protected AbstractCollQuery prepareQuery(Predicate predicate) { + protected AbstractCollQuery prepareQuery(@Nullable Predicate predicate) { CollQuery query = new CollQuery<>(); query.from(builder, findAll()); diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/package-info.java b/src/main/java/org/springframework/data/keyvalue/repository/support/package-info.java new file mode 100644 index 0000000..fd58c5a --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/package-info.java @@ -0,0 +1,6 @@ +/** + * Support infrastructure for query derivation of key/value specific repositories. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.keyvalue.repository.support; diff --git a/src/main/java/org/springframework/data/map/package-info.java b/src/main/java/org/springframework/data/map/package-info.java new file mode 100644 index 0000000..d184db6 --- /dev/null +++ b/src/main/java/org/springframework/data/map/package-info.java @@ -0,0 +1,6 @@ +/** + * Repository implementation backed by generic {@link java.util.Map} instances. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.map; diff --git a/src/main/java/org/springframework/data/map/repository/config/package-info.java b/src/main/java/org/springframework/data/map/repository/config/package-info.java new file mode 100644 index 0000000..346ace9 --- /dev/null +++ b/src/main/java/org/springframework/data/map/repository/config/package-info.java @@ -0,0 +1,6 @@ +/** + * Support infrastructure for the configuration of {@link java.util.Map} repositories. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.map.repository.config; diff --git a/src/test/java/org/springframework/data/keyvalue/core/IterableConverterUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/IterableConverterUnitTests.java index 2f9ebd7..0c5567f 100644 --- a/src/test/java/org/springframework/data/keyvalue/core/IterableConverterUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/core/IterableConverterUnitTests.java @@ -30,14 +30,10 @@ import org.junit.Test; /** * @author Christoph Strobl + * @author Mark Paluch */ public class IterableConverterUnitTests { - @Test // DATAKV-101 - public void toListShouldReturnEmptyListWhenSourceIsNull() { - assertThat(toList(null), notNullValue()); - } - @Test // DATAKV-101 public void toListShouldReturnEmptyListWhenSourceEmpty() { assertThat(toList(Collections.emptySet()), empty()); diff --git a/src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtilsUnitTests.java b/src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtilsUnitTests.java index 647abc1..8ca8249 100644 --- a/src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtilsUnitTests.java @@ -39,6 +39,7 @@ import com.querydsl.core.types.dsl.PathBuilder; * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch */ public class KeyValueQuerydslUtilsUnitTests { @@ -57,9 +58,9 @@ public class KeyValueQuerydslUtilsUnitTests { toOrderSpecifier(Sort.by("firstname"), null); } - @Test // DATACMNS-525 - public void toOrderSpecifierReturnsEmptyArrayWhenSortIsNull() { - assertThat(toOrderSpecifier(null, builder), arrayWithSize(0)); + @Test // DATACMNS-525, DATAKV-197 + public void toOrderSpecifierReturnsEmptyArrayWhenSortIsUnsorted() { + assertThat(toOrderSpecifier(Sort.unsorted(), builder), arrayWithSize(0)); } @Test // DATACMNS-525 diff --git a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java b/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java index d7e9c79..9dd2497 100644 --- a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java @@ -45,6 +45,7 @@ import com.google.common.collect.Lists; * @author Christoph Strobl * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch */ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitTests { @@ -76,8 +77,7 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT assertThat(page1.getContent(), hasSize(1)); assertThat(page1.hasNext(), is(true)); - Page page2 = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), - page1.nextPageable()); + Page page2 = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), page1.nextPageable()); assertThat(page2.getTotalElements(), is(2L)); assertThat(page2.getContent(), hasSize(1)); @@ -127,12 +127,17 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT assertThat(result, contains(TYRION, JAIME, CERSEI)); } - @Test // DATAKV-90 - public void findAllShouldIgnoreNullOrderSpecifier() { + @Test(expected = IllegalArgumentException.class) // DATAKV-90, DATAKV-197 + public void findAllShouldRequireSort() { + repository.findAll((QSort) null); + } + + @Test // DATAKV-90, DATAKV-197 + public void findAllShouldAllowUnsortedFindAll() { repository.saveAll(LENNISTERS); - Iterable result = repository.findAll((QSort) null); + Iterable result = repository.findAll(Sort.unsorted()); assertThat(result, containsInAnyOrder(TYRION, JAIME, CERSEI)); }