Browse Source

DATACMNS-525 - Introduce infrastructure for KeyValue repositories.

Added basic infrastructure for repositories on top of key-value data stores. For more details see the original pull request.

Original pull request: #95.
pull/106/merge
Christoph Strobl 12 years ago committed by Oliver Gierke
parent
commit
e2358f3bf8
  1. 23
      pom.xml
  2. 147
      src/main/asciidoc/key-value-repositories.adoc
  3. 65
      src/main/java/org/springframework/data/keyvalue/annotation/KeySpace.java
  4. 78
      src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java
  5. 39
      src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java
  6. 105
      src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java
  7. 28
      src/main/java/org/springframework/data/keyvalue/core/IdGenerator.java
  8. 106
      src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java
  9. 36
      src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java
  10. 179
      src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java
  11. 475
      src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java
  12. 354
      src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java
  13. 111
      src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java
  14. 39
      src/main/java/org/springframework/data/keyvalue/core/SortAccessor.java
  15. 49
      src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java
  16. 146
      src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComperator.java
  17. 105
      src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java
  18. 62
      src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java
  19. 47
      src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java
  20. 45
      src/main/java/org/springframework/data/keyvalue/core/mapping/BasicPersistentProperty.java
  21. 158
      src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java
  22. 149
      src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java
  23. 201
      src/main/java/org/springframework/data/keyvalue/ehcache/EhCacheKeyValueAdapter.java
  24. 153
      src/main/java/org/springframework/data/keyvalue/ehcache/EhCacheQueryEngine.java
  25. 51
      src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java
  26. 135
      src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java
  27. 128
      src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java
  28. 98
      src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastKeyValueAdapter.java
  29. 108
      src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java
  30. 135
      src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java
  31. 121
      src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java
  32. 166
      src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java
  33. 107
      src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java
  34. 200
      src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java
  35. 30
      src/main/java/org/springframework/data/keyvalue/repository/KeyValueRepository.java
  36. 156
      src/main/java/org/springframework/data/keyvalue/repository/QueryDslKeyValueRepository.java
  37. 132
      src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java
  38. 49
      src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java
  39. 104
      src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java
  40. 179
      src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java
  41. 288
      src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java
  42. 83
      src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java
  43. 108
      src/main/java/org/springframework/data/querydsl/QueryDslUtils.java
  44. 7
      src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java
  45. 9
      src/main/java/org/springframework/data/repository/query/DefaultEvaluationContextProvider.java
  46. 4
      src/main/java/org/springframework/data/repository/query/EvaluationContextProvider.java
  47. 6
      src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java
  48. 104
      src/test/java/org/springframework/data/keyvalue/Person.java
  49. 468
      src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java
  50. 688
      src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java
  51. 213
      src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java
  52. 405
      src/test/java/org/springframework/data/keyvalue/ehcache/KeyValueTemplateTestsUsingEhCache.java
  53. 105
      src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesUnitTests.java
  54. 42
      src/test/java/org/springframework/data/keyvalue/hazelcast/HazelcastUtils.java
  55. 405
      src/test/java/org/springframework/data/keyvalue/hazelcast/KeyValueTemplateTestsUsingHazelcast.java
  56. 102
      src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesUnitTests.java
  57. 170
      src/test/java/org/springframework/data/keyvalue/map/MapBackedKeyValueRepositoryUnitTests.java
  58. 96
      src/test/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactoryUnitTests.java
  59. 222
      src/test/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterUnitTests.java
  60. 138
      src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java
  61. 206
      src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java
  62. 92
      src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarUnitTests.java
  63. 567
      src/test/java/org/springframework/data/keyvalue/repository/query/SpELQueryCreatorUnitTests.java
  64. 24
      src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
  65. 120
      src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java
  66. 2
      template.mf

23
pom.xml

@ -104,6 +104,13 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-collections</artifactId>
<version>${querydsl}</version>
<optional>true</optional>
</dependency>
<!-- EJB Transactions --> <!-- EJB Transactions -->
<dependency> <dependency>
<groupId>javax.ejb</groupId> <groupId>javax.ejb</groupId>
@ -148,6 +155,7 @@
<version>${springhateoas}</version> <version>${springhateoas}</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>
@ -175,6 +183,21 @@
<version>1.8.6</version> <version>1.8.6</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.3</version>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
<build> <build>

147
src/main/asciidoc/key-value-repositories.adoc

@ -0,0 +1,147 @@
:spring-framework-docs: http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html
[[key-value]]
= Key Value Repositories
This chapter explains concepts and usage patterns when working with the key value abstraction and the `java.util.Map` based implementation provided by Spring Data Commons.
[[key-value.core-concepts]]
== Core Concepts
The Key/Value abstraction within Spring Data Commons requires an `Adapter` shielding the native store implementation freeing up `KeyValueTemplate` to work on top of any key/value pair like structure. Keys are distributed across <<key-value.keyspaces>>. Unless otherwise specified the class name is used as the default keyspace for an entity.
[source, java]
----
interface KeyValueOperations {
<T> T insert(T objectToInsert); <1>
void update(Object objectToUpdate); <2>
void delete(Class<?> type); <3>
<T> T findById(Serializable id, Class<T> type); <4>
<T> List<T> findAllOf(Class<T> type); <5>
<T> List<T> find(KeyValueQuery<?> query, Class<T> type); <6>
//... more functionality omitted.
}
----
<1> Inserts the given entity and assigns id if required.
<2> Updates the given entity.
<3> Removes all entities of matching type.
<4> Returns the entity of given type with matching id.
<5> Returns all entities of matching type.
<6> Returns a List of all entities of given type matching the criteria of the query.
[[key-value.template-configuration]]
== Configuring The KeyValueTemplate
In its very basic shape the `KeyValueTemplate` uses a `MapAdaper` wrapping a `ConcurrentHashMap` using link:{spring-framework-docs}/expressions.html[Spring Expression Language] to perform queries and sorting.
NOTE: The used `KeyValueAdapter` does the heavy lifting when it comes to storing and retrieving data. The data structure used will influence performance and/or multi threading behavior.
One may choose to use a different type or preinitialize the adapter with some values, and can do so using `MapKeyValueAdapterFactory`.
[source, java]
----
@Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(keyValueAdapter());
}
@Bean
public KeyValueAdapter keyValueAdapter() {
MapKeyValueAdapterFactory factory = new MapKeyValueAdapterFactory();
factory.setMapType(ConcurrentSkipListMap.class);
factory.setInitialValuesForKeyspace("lennister", singletonMap("1", "tyrion"));
factory.setInitialValuesForKeyspace("stark", singletonMap("1", "sansa"));
return factory.getAdapter();
}
----
[[key-value.keyspaces]]
== Keyspaces
Keyspaces define in which part of the data structure the entity should be kept. So this is a rather similar concept as collections in MongoDB and Elasticsearch, Cores in Solr, Tables in JPA.
By default the keyspace of an entity is extracted form its type, but one can also choose to store entities of different types within one keyspace. In that case any find operation will type check results.
[source, java]
----
@KeySpace("persons")
class Person {
@Id String id;
String firstname;
String lastname;
}
class User extends Person {
String username;
}
template.findAllOf(Person.class); <1>
template.findAllOf(User.class); <2>
----
<1> Returns all entities for keyspace "persons".
<2> Returns only elements of type `User` stored in keyspace "persons".
[[key-value.template-query]]
== Querying
Query execution is managed by the `QueryEngine`. As mentioned before it is possible to instruct the `KeyValueAdapter` to use an implementation specific `QueryEngine` that allows access to native functionality.
When used without further customization queries are be executed using a `SpELQueryEngine`.
NOTE: For performance reasons, we highly recommend to have at least Spring 4.1.2 or better to make use of link:{spring-framework-docs}/expressions.html#expressions-spel-compilation[compiled SpEL Expressions].
[source, java]
----
KeyValueQuery<String> query = new KeyValueQuery<String>("lastname == 'targaryen'");
List<Person> targaryens = template.find(query, Person.class);
----
WARNING: Please note that you need to have getters/setters present to query properties using SpEL.
[[key-value.template-sort]]
== Sorting
Depending on the store implementation provided by the adapter entities might already be stored in some sorted way but do not necessarily have to be. Again the underlying `QueryEngine` is capable of performing sort operations.
When used without further customization sorting is done using a `SpelPropertyComperator` extracted from the `Sort` clause provided
[source, java]
----
KeyValueQuery<String> query = new KeyValueQuery<String>("lastname == 'baratheon'");
query.setSort(new Sort(DESC, "age"));
List<Person> targaryens = template.find(query, Person.class);
----
WARNING: Please note that you need to have getters/setters present to sort using SpEL.
[[key-value.repositories]]
== Key Value Repositories
KeyValue repositories reside on top of the `KeyValaueTemplate`. Using the default `SpelQueryCreator` allows deriving query and sort expressions from the given methodname.
[source, java]
----
@Configuration
@EnableKeyValueRepositories
class KeyValueConfig {
@Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new MapKeyValueAdapter());
}
}
interface PersonRepository implements CrudRepository<Person, String> {
List<Person> findByLastname(String lastname);
}
----

65
src/main/java/org/springframework/data/keyvalue/annotation/KeySpace.java

@ -0,0 +1,65 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.annotation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.Persistent;
/**
* Marker interface for methods with {@link Persistent} annotations indicating the presence of a dedicated keyspace the
* entity should reside in. If present the value will be picked up for resolving the keyspace.
*
* <pre>
* <code>
* &#64;Persistent
* &#64;Documented
* &#64;Retention(RetentionPolicy.RUNTIME)
* &#64;Target({ ElementType.TYPE })
* public &#64;interface Document {
*
* &#64;KeySpace
* String collection() default "person";
* }
* </code>
* </pre>
*
* Can also be directly used on types to indicate the keyspace.
*
* <pre>
* <code>
* &#64;KeySpace("persons")
* public class Foo {
*
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.10
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { METHOD, TYPE })
public @interface KeySpace {
String value() default "";
}

78
src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java

@ -0,0 +1,78 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Base implementation of {@link KeyValueAdapter} holds {@link QueryEngine} to delegate {@literal find} and
* {@literal count} execution to.
*
* @author Christoph Strobl
* @since 1.10
*/
public abstract class AbstractKeyValueAdapter implements KeyValueAdapter {
private final QueryEngine<? extends KeyValueAdapter, ?, ?> engine;
/**
* Creates new {@link AbstractKeyValueAdapter} with using the default query engine.
*/
protected AbstractKeyValueAdapter() {
this(null);
}
/**
* Creates new {@link AbstractKeyValueAdapter} with using the default query engine.
*
* @param engine will be defaulted to {@link SpelQueryEngine} if {@literal null}.
*/
protected AbstractKeyValueAdapter(QueryEngine<? extends KeyValueAdapter, ?, ?> engine) {
this.engine = engine != null ? engine : new SpelQueryEngine<KeyValueAdapter>();
this.engine.registerAdapter(this);
}
/**
* Get the {@link QueryEngine} used.
*
* @return
*/
protected QueryEngine<? extends KeyValueAdapter, ?, ?> getQueryEngine() {
return engine;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.io.Serializable)
*/
@Override
public Collection<?> find(KeyValueQuery<?> query, Serializable keyspace) {
return engine.execute(query, keyspace);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#count(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.io.Serializable)
*/
@Override
public long count(KeyValueQuery<?> query, Serializable keyspace) {
return engine.count(query, keyspace);
}
}

39
src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java

@ -0,0 +1,39 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Resolves the criteria object from given {@link KeyValueQuery}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public interface CriteriaAccessor<T> {
/**
* Checks and reads {@link KeyValueQuery#getCritieria()} of given {@link KeyValueQuery}. Might also apply additional
* transformation to match the desired type.
*
* @param query can 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}
* .
*/
T resolve(KeyValueQuery<?> query);
}

105
src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java

@ -0,0 +1,105 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.UUID;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class IdAccessor {
private final PersistentEntity<?, ?> entity;
private final IdGenerator idGenerator;
private final BeanWrapper<?> wrapper;
@SuppressWarnings("rawtypes")
public IdAccessor(PersistentEntity<?, ? extends PersistentProperty> entity, BeanWrapper<?> wrapper) {
this(entity, wrapper, DefaultIdGenerator.INSTANCE);
}
@SuppressWarnings("rawtypes")
public IdAccessor(PersistentEntity<?, ? extends PersistentProperty> entity, BeanWrapper<?> wrapper,
IdGenerator idGenerator) {
Assert.notNull(entity, "PersistentEntity must not be 'null'");
Assert.notNull(wrapper, "BeanWrapper must not be 'null'.");
this.idGenerator = idGenerator != null ? idGenerator : DefaultIdGenerator.INSTANCE;
this.entity = entity;
this.wrapper = wrapper;
}
public <T> T getId() {
if (!entity.hasIdProperty()) {
throw new InvalidDataAccessApiUsageException(String.format("Cannot determine id for type %s", entity.getType()));
}
PersistentProperty<?> idProperty = entity.getIdProperty();
Object value = wrapper.getProperty(idProperty);
if (value == null) {
Serializable id = idGenerator.newIdForType(idProperty.getActualType());
wrapper.setProperty(idProperty, id);
return (T) id;
}
return (T) value;
}
/**
* @author Christoph Strobl
*/
static enum DefaultIdGenerator implements IdGenerator {
INSTANCE;
@Override
public Serializable newIdForType(Class<?> idType) {
if (ClassUtils.isAssignable(String.class, idType)) {
return UUID.randomUUID().toString();
} else if (ClassUtils.isAssignable(Integer.class, idType)) {
try {
return SecureRandom.getInstance("NativePRNGBlocking");
} catch (NoSuchAlgorithmException e) {
throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e);
}
} else if (ClassUtils.isAssignable(Long.class, idType)) {
try {
return SecureRandom.getInstance("NativePRNGBlocking").nextLong();
} catch (NoSuchAlgorithmException e) {
throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e);
}
}
throw new InvalidDataAccessApiUsageException("Non gereratable id type....");
}
}
}

28
src/main/java/org/springframework/data/keyvalue/core/IdGenerator.java

@ -0,0 +1,28 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
/**
* @author Christoph Strobl
* @since 1.10
*/
public interface IdGenerator {
Serializable newIdForType(Class<?> actualType);
}

106
src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java

@ -0,0 +1,106 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* {@link KeyValueAdapter} unifies access and shields the underlying key/value specific implementation.
*
* @author Christoph Strobl
* @since 1.10
*/
public interface KeyValueAdapter extends DisposableBean {
/**
* Add object with given id to keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return the item previously associated with the id.
*/
Object put(Serializable id, Object item, Serializable keyspace);
/**
* Check if a object with given id exists in keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return true if item of type with id exists.
*/
boolean contains(Serializable id, Serializable keyspace);
/**
* Get the object with given id from keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return {@literal null} in case no matching item exists.
*/
Object get(Serializable id, Serializable keyspace);
/**
* Delete and return the obect 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
*/
Object delete(Serializable id, Serializable keyspace);
/**
* Get all elements for given keyspace.
*
* @param keyspace must not be {@literal null}.
* @return empty {@link Collection} if nothing found.
*/
Collection<?> getAllOf(Serializable keyspace);
/**
* Remove all objects of given type.
*
* @param keyspace must not be {@literal null}.
*/
void deleteAllOf(Serializable keyspace);
/**
* Removes all objects.
*/
void clear();
/**
* Find all matching objects within {@literal keyspace}.
*
* @param query
* @param keyspace must not be {@literal null}.
* @return empty {@link Collection} if no match found.
*/
Collection<?> find(KeyValueQuery<?> query, Serializable keyspace);
/**
* Count all matching objects within {@literal keyspace}.
*
* @param query
* @param keyspace must not be {@literal null}.
* @return
*/
long count(KeyValueQuery<?> query, Serializable keyspace);
}

36
src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java

@ -0,0 +1,36 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
/**
* 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
* @since 1.10
* @param <T>
*/
public interface KeyValueCallback<T> {
/**
* Gets called by {@code KeyValueTemplate#execute(KeyValueCallback)}. Allows for returning a result object created
* within the callback, i.e. a domain object or a collection of domain objects.
*
* @param adapter
* @return
*/
T doInKeyValue(KeyValueAdapter adapter);
}

179
src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java

@ -0,0 +1,179 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.util.List;
import org.springframework.beans.factory.DisposableBean;
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;
/**
* Interface that specifies a basic set of key/value operations. Implemented by {@link KeyValueTemplate}.
*
* @author Christoph Strobl
* @since 1.10
*/
public interface KeyValueOperations extends DisposableBean {
/**
* Add given object. <br />
* Object needs to have id property to which a generated value will be assigned.
*
* @param objectToInsert
* @return
*/
<T> T insert(T objectToInsert);
/**
* Add object with given id.
*
* @param id must not be {@literal null}.
* @param objectToInsert must not be {@literal null}.
*/
void insert(Serializable id, Object objectToInsert);
/**
* Get all elements of given type. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type.
*
* @param type must not be {@literal null}.
* @return empty collection if no elements found.
*/
<T> List<T> findAll(Class<T> type);
/**
* Get all elements ordered by sort. Respects {@link KeySpace} if present and therefore returns all elements that can
* be assigned to requested type.
*
* @param sort must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
<T> List<T> findAll(Sort sort, Class<T> type);
/**
* Get element of given type with given id. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type.
*
* @param id must not be {@literal null}.
* @param type must not be {@literal null}.
* @return null if not found.
*/
<T> T findById(Serializable id, Class<T> type);
/**
* Execute operation against underlying store.
*
* @param action must not be {@literal null}.
* @return
*/
<T> T execute(KeyValueCallback<T> action);
/**
* Get all elements matching the given query. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type..
*
* @param query must not be {@literal null}.
* @param type must not be {@literal null}.
* @return empty collection if no match found.
*/
<T> List<T> find(KeyValueQuery<?> query, Class<T> type);
/**
* Get all elements in given range. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type.
*
* @param offset
* @param rows
* @param type must not be {@literal null}.
* @return
*/
<T> List<T> findInRange(int offset, int rows, Class<T> type);
/**
* Get all elements in given range ordered by sort. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type.
*
* @param offset
* @param rows
* @param sort
* @param type
* @return
*/
<T> List<T> findInRange(int offset, int rows, Sort sort, Class<T> type);
/**
* @param objectToUpdate must not be {@literal null}.
*/
void update(Object objectToUpdate);
/**
* @param id must not be {@literal null}.
* @param objectToUpdate must not be {@literal null}.
*/
void update(Serializable id, Object objectToUpdate);
/**
* Remove all elements of type. <br />
* Respects {@link KeySpace} if present and therefore removes all elements that can be assigned to requested type.
*
* @param type must not be {@literal null}.
*/
void delete(Class<?> type);
/**
* @param objectToDelete must not be {@literal null}.
* @return
*/
<T> T delete(T objectToDelete);
/**
* Delete item of type with given id.
*
* @param id must not be {@literal null}.
* @param type must not be {@literal null}.
* @return the deleted item or {@literal null} if no match found.
*/
<T> T delete(Serializable id, Class<T> type);
/**
* Total number of elements with given type available. <br />
* Respects {@link KeySpace} if present and therefore counts all elements that can be assigned to requested type.
*
* @param type must not be {@literal null}.
* @return
*/
long count(Class<?> type);
/**
* Total number of elements matching given query.<br />
* Respects {@link KeySpace} if present and therefore counts all elements that can be assigned to requested type.
*
* @param query
* @param type
* @return
*/
long count(KeyValueQuery<?> query, Class<?> type);
/**
* @return mapping context in use.
*/
MappingContext<?, ?> getMappingContext();
}

475
src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java

@ -0,0 +1,475 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.data.keyvalue.core.mapping.BasicMappingContext;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Basic implementation of {@link KeyValueOperations}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueTemplate implements KeyValueOperations {
private final KeyValueAdapter adapter;
private ConcurrentHashMap<Class<?>, String> keySpaceCache = new ConcurrentHashMap<Class<?>, String>();
@SuppressWarnings("rawtypes")//
private MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty>, ? extends PersistentProperty<?>> mappingContext;
/**
* Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} with a default
* {@link BasicMappingContext}.
*
* @param adapter must not be {@literal null}.
*/
public KeyValueTemplate(KeyValueAdapter adapter) {
this(adapter, new BasicMappingContext());
}
/**
* Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} and {@link MappingContext}.
*
* @param adapter must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@SuppressWarnings("rawtypes")
public KeyValueTemplate(
KeyValueAdapter adapter,
MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty>, ? extends PersistentProperty<?>> mappingContext) {
Assert.notNull(adapter, "Adapter must not be 'null' when intializing KeyValueTemplate.");
Assert.notNull(mappingContext, "MappingContext must not be 'null' when intializing KeyValueTemplate.");
this.adapter = adapter;
this.mappingContext = mappingContext;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.lang.Object)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> T insert(T objectToInsert) {
PersistentEntity<?, ? extends PersistentProperty> entity = this.mappingContext.getPersistentEntity(ClassUtils
.getUserClass(objectToInsert));
Serializable id = new IdAccessor(entity, BeanWrapper.create(objectToInsert, null)).getId();
insert(id, objectToInsert);
return objectToInsert;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.io.Serializable, java.lang.Object)
*/
@Override
public void insert(final Serializable id, final Object objectToInsert) {
Assert.notNull(id, "Id for object to be inserted must not be 'null'.");
Assert.notNull(objectToInsert, "Object to be inserted must not be 'null'.");
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
String typeKey = resolveKeySpace(objectToInsert.getClass());
if (adapter.contains(id, typeKey)) {
throw new InvalidDataAccessApiUsageException("Cannot insert existing object. Please use update.");
}
adapter.put(id, objectToInsert, typeKey);
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.lang.Object)
*/
@SuppressWarnings("rawtypes")
@Override
public void update(Object objectToUpdate) {
PersistentEntity<?, ? extends PersistentProperty> entity = this.mappingContext.getPersistentEntity(ClassUtils
.getUserClass(objectToUpdate));
if (!entity.hasIdProperty()) {
throw new InvalidDataAccessApiUsageException(String.format("Cannot determine id for type %s",
ClassUtils.getUserClass(objectToUpdate)));
}
Serializable id = BeanWrapper.create(objectToUpdate, null).getProperty(entity.getIdProperty(), Serializable.class);
update(id, objectToUpdate);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.io.Serializable, java.lang.Object)
*/
@Override
public void update(final Serializable id, final Object objectToUpdate) {
Assert.notNull(id, "Id for object to be inserted must not be 'null'.");
Assert.notNull(objectToUpdate, "Object to be updated must not be 'null'. Use delete to remove.");
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
adapter.put(id, objectToUpdate, resolveKeySpace(objectToUpdate.getClass()));
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findAll(final Class<T> type) {
Assert.notNull(type, "Type to fetch must not be 'null'.");
return execute(new KeyValueCallback<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> doInKeyValue(KeyValueAdapter adapter) {
Collection<?> x = adapter.getAllOf(resolveKeySpace(type));
if (getKeySpace(type) == null) {
return new ArrayList<T>((Collection<T>) x);
}
ArrayList<T> filtered = new ArrayList<T>();
for (Object candidate : x) {
if (typeCheck(type, candidate)) {
filtered.add((T) candidate);
}
}
return filtered;
}
});
}
/*
*(non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findById(java.io.Serializable, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> T findById(final Serializable id, final Class<T> type) {
Assert.notNull(id, "Id for object to be inserted must not be 'null'.");
Assert.notNull(type, "Type to fetch must not be 'null'.");
return execute(new KeyValueCallback<T>() {
@SuppressWarnings("unchecked")
@Override
public T doInKeyValue(KeyValueAdapter adapter) {
Object result = adapter.get(id, resolveKeySpace(type));
if (result == null || getKeySpace(type) == null || typeCheck(type, result)) {
return (T) result;
}
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Class)
*/
@Override
public void delete(final Class<?> type) {
Assert.notNull(type, "Type to delete must not be 'null'.");
final String typeKey = resolveKeySpace(type);
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
adapter.deleteAllOf(typeKey);
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Object)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public <T> T delete(T objectToDelete) {
Class<T> type = (Class<T>) ClassUtils.getUserClass(objectToDelete);
PersistentEntity<?, ? extends PersistentProperty> entity = this.mappingContext.getPersistentEntity(type);
Serializable id = new IdAccessor(entity, BeanWrapper.create(objectToDelete, null)).getId();
return delete(id, type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.io.Serializable, java.lang.Class)
*/
@Override
public <T> T delete(final Serializable id, final Class<T> type) {
Assert.notNull(id, "Id for object to be inserted must not be 'null'.");
Assert.notNull(type, "Type to delete must not be 'null'.");
return execute(new KeyValueCallback<T>() {
@SuppressWarnings("unchecked")
@Override
public T doInKeyValue(KeyValueAdapter adapter) {
return (T) adapter.delete(id, resolveKeySpace(type));
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#count(java.lang.Class)
*/
@Override
public long count(Class<?> type) {
Assert.notNull(type, "Type for count must not be null!");
return findAll(type).size();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#execute(org.springframework.data.keyvalue.core.KeyValueCallback)
*/
@Override
public <T> T execute(KeyValueCallback<T> action) {
Assert.notNull(action, "KeyValueCallback must not be 'null'.");
try {
return action.doInKeyValue(this.adapter);
} catch (RuntimeException e) {
// TODO: potentially convert runtime exception?
throw e;
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class)
*/
@Override
public <T> List<T> find(final KeyValueQuery<?> query, final Class<T> type) {
return execute(new KeyValueCallback<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> doInKeyValue(KeyValueAdapter adapter) {
Collection<?> result = adapter.find(query, resolveKeySpace(type));
if (getKeySpace(type) == null) {
return new ArrayList<T>((Collection<T>) result);
}
ArrayList<T> filtered = new ArrayList<T>();
for (Object candidate : result) {
if (typeCheck(type, candidate)) {
filtered.add((T) candidate);
}
}
return filtered;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(org.springframework.data.domain.Sort, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findAll(Sort sort, Class<T> type) {
return find(new KeyValueQuery(sort), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(int, int, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findInRange(int offset, int rows, Class<T> type) {
return find(new KeyValueQuery().skip(offset).limit(rows), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(int, int, org.springframework.data.domain.Sort, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findInRange(int offset, int rows, Sort sort, Class<T> type) {
return find(new KeyValueQuery(sort).skip(offset).limit(rows), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#count(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class)
*/
@Override
public long count(final KeyValueQuery<?> query, final Class<?> type) {
return execute(new KeyValueCallback<Long>() {
@Override
public Long doInKeyValue(KeyValueAdapter adapter) {
return adapter.count(query, resolveKeySpace(type));
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#getMappingContext()
*/
@Override
public MappingContext<?, ?> getMappingContext() {
return this.mappingContext;
}
protected String resolveKeySpace(Class<?> type) {
Class<?> userClass = ClassUtils.getUserClass(type);
String potentialAlias = keySpaceCache.get(userClass);
if (potentialAlias != null) {
return potentialAlias;
}
String keySpaceString = null;
Object keySpace = getKeySpace(type);
if (keySpace != null) {
keySpaceString = keySpace.toString();
}
if (!StringUtils.hasText(keySpaceString)) {
keySpaceString = userClass.getName();
}
keySpaceCache.put(userClass, keySpaceString);
return keySpaceString;
}
/**
* Looks up {@link Persistent} when used as meta annotation to find the {@link KeySpace} attribute.
*
* @return
* @since 1.10
*/
Object getKeySpace(Class<?> type) {
KeySpace keyspace = AnnotationUtils.findAnnotation(type, KeySpace.class);
if (keyspace != null) {
return AnnotationUtils.getValue(keyspace);
}
AnnotationDescriptor<Persistent> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(type, Persistent.class);
if (descriptor != null && descriptor.getComposedAnnotation() != null) {
Annotation composed = descriptor.getComposedAnnotation();
for (Method method : descriptor.getComposedAnnotationType().getDeclaredMethods()) {
keyspace = AnnotationUtils.findAnnotation(method, KeySpace.class);
if (keyspace != null) {
return AnnotationUtils.getValue(composed, method.getName());
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
this.adapter.clear();
}
private boolean typeCheck(Class<?> requiredType, Object candidate) {
if (candidate == null) {
return true;
}
return ClassUtils.isAssignable(requiredType, candidate.getClass());
}
}

354
src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java

@ -0,0 +1,354 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@code MetaAnnotationUtils} is a collection of utility methods that complements the standard support already
* available in {@link AnnotationUtils}.
* <p>
* Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or <em>finding</em> an annotation,
* {@code MetaAnnotationUtils} goes a step further by providing support for determining the <em>root class</em> on which
* an annotation is declared, either directly or indirectly via a <em>composed
* annotation</em>. This additional information is encapsulated in an {@link AnnotationDescriptor}.
* <p>
* The additional information provided by an {@code AnnotationDescriptor} is required by the
* <em>Spring TestContext Framework</em> in order to be able to support class hierarchy traversals for annotations such
* as {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
* {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, and
* {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles} which offer support for merging and
* overriding various <em>inherited</em> annotation attributes (e.g.,
* {@link org.springframework.test.context.ContextConfiguration#inheritLocations}).
*
* @author Sam Brannen
* @since 4.0
* @see AnnotationUtils
* @see AnnotationDescriptor
*/
public abstract class MetaAnnotationUtils {
private MetaAnnotationUtils() {
/* no-op */
}
/**
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} on the supplied {@link Class},
* traversing its annotations and superclasses if no annotation can be found on the given class itself.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for the annotation on the given class and return a corresponding {@code AnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> means that the search process continues by returning to step #1 with
* the current annotation or superclass as the class to look for annotations on.
* <p>
* If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance hierarchy
* for interfaces will not be traversed.
*
* @param clazz the class to look for annotations on
* @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
* @see #findAnnotationDescriptorForTypes(Class, Class...)
*/
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Class<T> annotationType) {
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, avoiding endless recursion by
* tracking which annotations have already been <em>visited</em>.
*
* @param clazz the class to look for annotations on
* @param visited the set of annotations that have already been visited
* @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null}
*/
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Set<Annotation> visited, Class<T> annotationType) {
Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
// Declared locally?
if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
return new AnnotationDescriptor<T>(clazz, clazz.getAnnotation(annotationType));
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited,
annotationType);
if (descriptor != null) {
return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), composedAnnotation,
descriptor.getAnnotation());
}
}
}
// Declared on a superclass?
return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
}
/**
* Find the {@link UntypedAnnotationDescriptor} for the first {@link Class} in the inheritance hierarchy of the
* specified {@code clazz} (including the specified {@code clazz} itself) which declares at least one of the specified
* {@code annotationTypes}.
* <p>
* This method traverses the annotations and superclasses of the specified {@code clazz} if no annotation can be found
* on the given class itself.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for a local declaration of one of the annotation types on the given class and return a corresponding
* {@code UntypedAnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> means that the search process continues by returning to step #1 with
* the current annotation or superclass as the class to look for annotations on.
* <p>
* If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance hierarchy
* for interfaces will not be traversed.
*
* @param clazz the class to look for annotations on
* @param annotationTypes the types of annotations to look for
* @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
* @see #findAnnotationDescriptor(Class, Class)
*/
@SuppressWarnings("unchecked")
public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
Class<? extends Annotation>... annotationTypes) {
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, avoiding endless
* recursion by tracking which annotations have already been <em>visited</em>.
*
* @param clazz the class to look for annotations on
* @param visited the set of annotations that have already been visited
* @param annotationTypes the types of annotations to look for
* @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null}
*/
@SuppressWarnings("unchecked")
private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz, Set<Annotation> visited,
Class<? extends Annotation>... annotationTypes) {
assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
// Declared locally?
for (Class<? extends Annotation> annotationType : annotationTypes) {
if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType));
}
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(composedAnnotation.annotationType(),
visited, annotationTypes);
if (descriptor != null) {
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation,
descriptor.getAnnotation());
}
}
}
// Declared on a superclass?
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
}
/**
* Descriptor for an {@link Annotation}, including the {@linkplain #getDeclaringClass() class} on which the annotation
* is <em>declared</em> as well as the actual {@linkplain #getAnnotation() annotation} instance.
* <p>
* If the annotation is used as a meta-annotation, the descriptor also includes the
* {@linkplain #getComposedAnnotation() composed annotation} on which the annotation is present. In such cases, the
* <em>root declaring class</em> is not directly annotated with the annotation but rather indirectly via the composed
* annotation.
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code TransactionalTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
* <li>declaringClass: {@code TransactionalTests} class object</li>
* <li>composedAnnotation: {@code null}</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* public class TransactionalTests {}
* </pre>
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code UserRepositoryTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
* <li>declaringClass: {@code RepositoryTests} class object</li>
* <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* &#064;Retention(RetentionPolicy.RUNTIME)
* public @interface RepositoryTests {
* }
*
* &#064;RepositoryTests
* public class UserRepositoryTests {}
* </pre>
*
* @author Sam Brannen
* @since 4.0
*/
public static class AnnotationDescriptor<T extends Annotation> {
private final Class<?> rootDeclaringClass;
private final Class<?> declaringClass;
private final Annotation composedAnnotation;
private final T annotation;
private final AnnotationAttributes annotationAttributes;
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
}
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass, Annotation composedAnnotation,
T annotation) {
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
Assert.notNull(annotation, "annotation must not be null");
this.rootDeclaringClass = rootDeclaringClass;
this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation;
this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass, annotation
.annotationType().getName());
}
public Class<?> getRootDeclaringClass() {
return this.rootDeclaringClass;
}
public Class<?> getDeclaringClass() {
return this.declaringClass;
}
public T getAnnotation() {
return this.annotation;
}
public Class<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType();
}
public AnnotationAttributes getAnnotationAttributes() {
return this.annotationAttributes;
}
public Annotation getComposedAnnotation() {
return this.composedAnnotation;
}
public Class<? extends Annotation> getComposedAnnotationType() {
return this.composedAnnotation == null ? null : this.composedAnnotation.annotationType();
}
/**
* Provide a textual representation of this {@code AnnotationDescriptor}.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("rootDeclaringClass", rootDeclaringClass)//
.append("declaringClass", declaringClass)//
.append("composedAnnotation", composedAnnotation)//
.append("annotation", annotation)//
.toString();
}
}
/**
* <em>Untyped</em> extension of {@code AnnotationDescriptor} that is used to describe the declaration of one of
* several candidate annotation types where the actual annotation type cannot be predetermined.
*
* @author Sam Brannen
* @since 4.0
*/
public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Annotation> {
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
}
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
Annotation composedAnnotation, Annotation annotation) {
super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
}
}
private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes, String message) {
if (ObjectUtils.isEmpty(annotationTypes)) {
throw new IllegalArgumentException(message);
}
for (Class<?> clazz : annotationTypes) {
if (!Annotation.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Array elements must be of type Annotation");
}
}
}
}

111
src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java

@ -0,0 +1,111 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Base implementation for accessing and executing {@link KeyValueQuery} against a {@link KeyValueAdapter}.
*
* @author Christoph Strobl
* @since 1.10
* @param <ADAPTER>
* @param <CRITERIA>
* @param <SORT>
*/
public abstract class QueryEngine<ADAPTER extends KeyValueAdapter, CRITERIA, SORT> {
private final CriteriaAccessor<CRITERIA> criteriaAccessor;
private final SortAccessor<SORT> sortAccessor;
private ADAPTER adapter;
public QueryEngine(CriteriaAccessor<CRITERIA> criteriaAccessor, SortAccessor<SORT> sortAccessor) {
this.criteriaAccessor = criteriaAccessor;
this.sortAccessor = sortAccessor;
}
/**
* Extract query attributes and delegate to concrete execution.
*
* @param query
* @param keyspace
* @return
*/
public Collection<?> execute(KeyValueQuery<?> query, Serializable keyspace) {
CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null;
SORT sort = this.sortAccessor != null ? this.sortAccessor.resolve(query) : null;
return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace);
}
/**
* Extract query attributes and delegate to concrete execution.
*
* @param query
* @param keyspace
* @return
*/
public long count(KeyValueQuery<?> query, Serializable keyspace) {
CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null;
return count(criteria, keyspace);
}
/**
* @param criteria
* @param sort
* @param offset
* @param rows
* @param keyspace
* @return
*/
public abstract Collection<?> execute(CRITERIA criteria, SORT sort, int offset, int rows, Serializable keyspace);
/**
* @param criteria
* @param keyspace
* @return
*/
public abstract long count(CRITERIA criteria, Serializable keyspace);
/**
* Get the {@link KeyValueAdapter} used.
*
* @return
*/
protected ADAPTER getAdapter() {
return this.adapter;
}
/**
* @param adapter
*/
@SuppressWarnings("unchecked")
public void registerAdapter(KeyValueAdapter adapter) {
if (this.adapter == null) {
this.adapter = (ADAPTER) adapter;
} else {
throw new IllegalArgumentException("Cannot register more than one adapter for this QueryEngine.");
}
}
}

39
src/main/java/org/springframework/data/keyvalue/core/SortAccessor.java

@ -0,0 +1,39 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Resolves the {@link Sort} object from given {@link KeyValueQuery} and potentially converts it into a store specific
* representation that can be used by the {@link QueryEngine} implementation.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public interface SortAccessor<T> {
/**
* Reads {@link KeyValueQuery#getSort()} of given {@link KeyValueQuery} and applies required transformation to match
* the desired type.
*
* @param query can be {@literal null}.
* @return {@literal null} in case {@link Sort} has not been defined on {@link KeyValueQuery}.
*/
T resolve(KeyValueQuery<?> query);
}

49
src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java

@ -0,0 +1,49 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory;
import org.springframework.expression.spel.standard.SpelExpression;
/**
* {@link CriteriaAccessor} implementation capable of {@link SpelExpression}s.
*
* @author Christoph Strobl
* @since 1.10
*/
public enum SpelCriteriaAccessor implements CriteriaAccessor<SpelExpression> {
INSTANCE;
@Override
public SpelExpression resolve(KeyValueQuery<?> query) {
if (query.getCritieria() == null) {
return null;
}
if (query.getCritieria() instanceof SpelExpression) {
return (SpelExpression) query.getCritieria();
}
if (query.getCritieria() instanceof String) {
return SpelExpressionFactory.parseRaw((String) query.getCritieria());
}
throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCritieria());
}
}

146
src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComperator.java

@ -0,0 +1,146 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.util.Comparator;
import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory;
import org.springframework.expression.spel.standard.SpelExpression;
/**
* {@link Comparator} implementation using {@link SpelExpression}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public class SpelPropertyComperator<T> implements Comparator<T> {
private final String path;
private SpelExpression expression;
private boolean asc = true;
private boolean nullsFirst = true;
/**
* Create new {@link SpelPropertyComperator} comparing given property path.
*
* @param path
*/
public SpelPropertyComperator(String path) {
this.path = path;
}
/**
* Sort {@literal ascending}.
*
* @return
*/
public SpelPropertyComperator<T> asc() {
this.asc = true;
return this;
}
/**
* Sort {@literal descending}.
*
* @return
*/
public SpelPropertyComperator<T> desc() {
this.asc = false;
return this;
}
/**
* Sort {@literal null} values first.
*
* @return
*/
public SpelPropertyComperator<T> nullsFirst() {
this.nullsFirst = true;
return this;
}
/**
* Sort {@literal null} values last.
*
* @return
*/
public SpelPropertyComperator<T> nullsLast() {
this.nullsFirst = false;
return this;
}
/**
* Parse values to {@link SpelExpression}
*
* @return
*/
protected SpelExpression getExpression() {
if (this.expression == null) {
this.expression = SpelExpressionFactory.parseRaw(buildExpressionForPath());
}
return this.expression;
}
/**
* Create the expression raw value.
*
* @return
*/
protected String buildExpressionForPath() {
StringBuilder rawExpression = new StringBuilder(
"new org.springframework.util.comparator.NullSafeComparator(new org.springframework.util.comparator.ComparableComparator(), "
+ Boolean.toString(this.nullsFirst) + ").compare(");
rawExpression.append("#arg1?.");
rawExpression.append(path != null ? path.replace(".", "?.") : "");
rawExpression.append(",");
rawExpression.append("#arg2?.");
rawExpression.append(path != null ? path.replace(".", "?.") : "");
rawExpression.append(")");
return rawExpression.toString();
}
/*
* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(T arg1, T arg2) {
SpelExpression expressionToUse = getExpression();
expressionToUse.getEvaluationContext().setVariable("arg1", arg1);
expressionToUse.getEvaluationContext().setVariable("arg2", arg2);
return expressionToUse.getValue(Integer.class) * (asc ? 1 : -1);
}
/**
* Get dot path to property.
*
* @return
*/
public String getPath() {
return path;
}
}

105
src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java

@ -0,0 +1,105 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpression;
/**
* {@link QueryEngine} implementation specific for executing {@link SpelExpression} based {@link KeyValueQuery} against
* {@link KeyValueAdapter}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public class SpelQueryEngine<T extends KeyValueAdapter> extends
QueryEngine<KeyValueAdapter, SpelExpression, Comparator<?>> {
public SpelQueryEngine() {
super(SpelCriteriaAccessor.INSTANCE, SpelSortAccessor.INSTNANCE);
}
@Override
public Collection<?> execute(SpelExpression criteria, Comparator<?> sort, int offset, int rows, Serializable keyspace) {
return sortAndFilterMatchingRange(getAdapter().getAllOf(keyspace), criteria, sort, offset, rows);
}
@Override
public long count(SpelExpression criteria, Serializable keyspace) {
return filterMatchingRange(getAdapter().getAllOf(keyspace), criteria, -1, -1).size();
}
@SuppressWarnings({ "unchecked" })
private List<?> sortAndFilterMatchingRange(Collection<?> source, SpelExpression criteria, Comparator sort,
int offset, int rows) {
List<?> tmp = new ArrayList(source);
if (sort != null) {
Collections.sort(tmp, sort);
}
return filterMatchingRange(tmp, criteria, offset, rows);
}
private <S> List<S> filterMatchingRange(Iterable<S> source, SpelExpression criteria, int offset, int rows) {
List<S> result = new ArrayList<S>();
boolean compareOffsetAndRows = 0 < offset || 0 <= rows;
int remainingRows = rows;
int curPos = 0;
for (S candidate : source) {
boolean matches = criteria == null;
if (!matches) {
try {
matches = criteria.getValue(candidate, Boolean.class);
} catch (SpelEvaluationException e) {
criteria.getEvaluationContext().setVariable("it", candidate);
matches = criteria.getValue(Boolean.class);
}
}
if (matches) {
if (compareOffsetAndRows) {
if (curPos >= offset && rows > 0) {
result.add(candidate);
remainingRows--;
if (remainingRows <= 0) {
break;
}
}
curPos++;
} else {
result.add(candidate);
}
}
}
return result;
}
}

62
src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java

@ -0,0 +1,62 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import java.util.Comparator;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.NullHandling;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.util.comparator.CompoundComparator;
/**
* {@link SortAccessor} implementation capable of creating {@link SpelPropertyComperator}.
*
* @author Christoph Strobl
*/
public enum SpelSortAccessor implements SortAccessor<Comparator<?>> {
INSTNANCE;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Comparator<?> resolve(KeyValueQuery<?> query) {
if (query == null || query.getSort() == null) {
return null;
}
CompoundComparator compoundComperator = new CompoundComparator();
for (Order order : query.getSort()) {
SpelPropertyComperator<?> spelSort = new SpelPropertyComperator(order.getProperty());
if (Direction.DESC.equals(order.getDirection())) {
spelSort.desc();
if (order.getNullHandling() != null && !NullHandling.NATIVE.equals(order.getNullHandling())) {
spelSort = NullHandling.NULLS_FIRST.equals(order.getNullHandling()) ? spelSort.nullsFirst() : spelSort
.nullsLast();
}
}
compoundComperator.addComparator(spelSort);
}
return compoundComperator;
}
}

47
src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java

@ -0,0 +1,47 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core.mapping;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class BasicMappingContext extends
AbstractMappingContext<BasicPersistentEntity<?, BasicPersistentProperty>, BasicPersistentProperty> {
@Override
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
return new BasicPersistentEntity<T, BasicPersistentProperty>(typeInformation);
}
@Override
protected BasicPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
return new BasicPersistentProperty(field, descriptor, owner, simpleTypeHolder);
}
}

45
src/main/java/org/springframework/data/keyvalue/core/mapping/BasicPersistentProperty.java

@ -0,0 +1,45 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core.mapping;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Most trivial implementation of {@link PersistentProperty}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class BasicPersistentProperty extends AnnotationBasedPersistentProperty<BasicPersistentProperty> {
public BasicPersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
PersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
@Override
protected Association<BasicPersistentProperty> createAssociation() {
return new Association<BasicPersistentProperty>(this, null);
}
}

158
src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java

@ -0,0 +1,158 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core.query;
import org.springframework.data.domain.Sort;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T> Criteria type
*/
public class KeyValueQuery<T> {
private Sort sort;
private int offset = -1;
private int rows = -1;
private T criteria;
/**
* Creates new instance of {@link KeyValueQuery}.
*/
public KeyValueQuery() {}
/**
* Creates new instance of {@link KeyValueQuery} with given criteria.
*
* @param criteria can be {@literal null}.
*/
public KeyValueQuery(T criteria) {
this.criteria = criteria;
}
/**
* Creates new instance of {@link KeyValueQuery} with given {@link Sort}.
*
* @param sort can be {@literal null}.
*/
public KeyValueQuery(Sort sort) {
this.sort = sort;
}
/**
* Get the criteria object.
*
* @return
*/
public T getCritieria() {
return criteria;
}
/**
* Get {@link Sort}.
*
* @return
*/
public Sort getSort() {
return sort;
}
/**
* Number of elements to skip.
*
* @return negative value if not set.
*/
public int getOffset() {
return this.offset;
}
/**
* Number of elements to read.
*
* @return negative value if not set.
*/
public int getRows() {
return this.rows;
}
/**
* Set the number of elements to skip.
*
* @param offset use negative value for none.
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* Set the number of elements to read.
*
* @param offset use negative value for all.
*/
public void setRows(int rows) {
this.rows = rows;
}
/**
* Set {@link Sort} to be applied.
*
* @param sort
*/
public void setSort(Sort sort) {
this.sort = sort;
}
/**
* Add given {@link Sort}.
*
* @param sort {@literal null} {@link Sort} will be ignored.
* @return
*/
public KeyValueQuery<T> orderBy(Sort sort) {
if (sort == null) {
return this;
}
if (this.sort != null) {
this.sort.and(sort);
} else {
this.sort = sort;
}
return this;
}
/**
* @see KeyValueQuery#setOffset(int)
* @param offset
* @return
*/
public KeyValueQuery<T> skip(int offset) {
setOffset(offset);
return this;
}
/**
* @see KeyValueQuery#setRows(int)
* @param rows
* @return
*/
public KeyValueQuery<T> limit(int rows) {
setRows(rows);
return this;
}
}

149
src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java

@ -0,0 +1,149 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core.spel;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
/**
* Factory capable of parsing raw expression strings taking Spring 4.1 compiled expressions into concern. Will fall back
* to non compiled ones in case lower than 4.1 Spring version is detected or expression compilation fails.
*
* @author Christoph Strobl
* @since 1.10
*/
public class SpelExpressionFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(SpelExpressionFactory.class);
private static final boolean IS_SPEL_COMPILER_PRESENT = ClassUtils.isPresent(
"org.springframework.expression.spel.standard.SpelCompiler", SpelExpressionFactory.class.getClassLoader());
private static final SpelParserConfiguration DEFAULT_PARSER_CONFIG = new SpelParserConfiguration(false, false);
private static SpelExpressionParser compiledModeExpressionParser;
private static SpelExpressionParser expressionParser;
static {
expressionParser = new SpelExpressionParser(DEFAULT_PARSER_CONFIG);
if (IS_SPEL_COMPILER_PRESENT) {
SpelExpressionParser parser = new SpelExpressionParser(silentlyInitializeCompiledMode("IMMEDIATE"));
if (usesPatchedSpelCompilerThatAllowsReferenceToContextVariables(parser)) {
compiledModeExpressionParser = parser;
}
}
}
/**
* @param expressionString
* @return
*/
public static SpelExpression parseRaw(String expressionString) {
if (compiledModeExpressionParser != null) {
try {
return compileSpelExpression(compiledModeExpressionParser.parseRaw(expressionString));
} catch (ExpressionException e) {
LOGGER.info(e.getMessage(), e);
}
}
return expressionParser.parseRaw(expressionString);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static SpelParserConfiguration silentlyInitializeCompiledMode(String mode) {
try {
Class compilerMode = ClassUtils.forName("org.springframework.expression.spel.SpelCompilerMode",
SpelExpressionFactory.class.getClassLoader());
Constructor<SpelParserConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
SpelParserConfiguration.class, compilerMode, ClassLoader.class);
if (constructor != null) {
return BeanUtils.instantiateClass(constructor, Enum.valueOf(compilerMode, mode.toUpperCase()),
SpelExpressionFactory.class.getClassLoader());
}
} catch (Exception e) {
LOGGER.info(String.format("Could not create SpelParserConfiguration for mode '%s'.", mode), e);
}
return DEFAULT_PARSER_CONFIG;
}
private static SpelExpression compileSpelExpression(SpelExpression compilableExpression) {
try {
MethodInvoker mi = new MethodInvoker();
mi.setTargetObject(compilableExpression);
mi.setTargetMethod("compileExpression");
mi.prepare();
mi.invoke();
return compilableExpression;
} catch (ExpressionException ex) {
throw new ExpressionException(String.format("Could parse expression %s in compiled mode. Using fallback.",
compilableExpression.getExpressionString()), ex);
} catch (IllegalAccessException ex) {
throw new ExpressionException("o_O failed to invoke compileExpression. Are you using at least Spring 4.1?", ex);
} catch (NoSuchMethodException ex) {
throw new ExpressionException("o_O missing method compileExpression. Using fallback.", ex);
} catch (ClassNotFoundException ex) {
throw new ExpressionException("o_O missing class SpelExpression.", ex);
} catch (InvocationTargetException ex) {
throw new ExpressionException("o_O failed to invoke compileExpression. Are you using at least Spring 4.1?", ex);
}
}
/**
* @see SPR-12326, SPR-12359
* @param parser
* @return
*/
private static boolean usesPatchedSpelCompilerThatAllowsReferenceToContextVariables(SpelExpressionParser parser) {
SpelExpression ex = parser.parseRaw("#foo == 1");
ex.getEvaluationContext().setVariable("foo", 1);
try {
for (int i = 0; i < 3; i++) {
ex.getValue(Boolean.class);
}
return true;
} catch (Exception e) {
LOGGER.info("Compiled SpEL sanity check failed. Falling back to non compiled mode");
}
return false;
}
}

201
src/main/java/org/springframework/data/keyvalue/ehcache/EhCacheKeyValueAdapter.java vendored

@ -0,0 +1,201 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.SearchAttribute;
import net.sf.ehcache.config.Searchable;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Direction;
import net.sf.ehcache.search.expression.Criteria;
import org.springframework.beans.BeanUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
import org.springframework.data.keyvalue.core.QueryEngine;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
/**
* @author Christoph Strobl
*/
public class EhCacheKeyValueAdapter extends AbstractKeyValueAdapter {
private CacheManager cacheManager;
public EhCacheKeyValueAdapter() {
this(CacheManager.create());
}
public EhCacheKeyValueAdapter(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public EhCacheKeyValueAdapter(
QueryEngine<EhCacheKeyValueAdapter, Criteria, Map<Attribute<?>, Direction>> queryEngine, CacheManager cacheManager) {
super(queryEngine);
this.cacheManager = cacheManager;
}
@Override
public Object put(Serializable id, Object item, Serializable keyspace) {
Assert.notNull(id, "Id must not be 'null' for adding.");
Assert.notNull(item, "Item must not be 'null' for adding.");
Element element = new Element(id, item);
getCache(keyspace, item.getClass()).put(element);
return item;
}
@Override
public boolean contains(Serializable id, Serializable keyspace) {
return get(id, keyspace) != null;
}
@Override
public Object get(Serializable id, Serializable keyspace) {
Cache cache = getCache(keyspace);
if (cache == null) {
return null;
}
Element element = cache.get(id);
return ElementConverter.INSTANCE.convert(element);
}
@Override
public Object delete(Serializable id, Serializable keyspace) {
Cache cache = getCache(keyspace);
if (cache == null) {
return null;
}
Element element = cache.removeAndReturnElement(id);
return ElementConverter.INSTANCE.convert(element);
}
@Override
public Collection<?> getAllOf(Serializable keyspace) {
Cache cache = getCache(keyspace);
if (cache == null) {
return Collections.emptyList();
}
Collection<Element> values = cache.getAll(cache.getKeys()).values();
return new ListConverter<Element, Object>(ElementConverter.INSTANCE).convert(values);
}
@Override
public void deleteAllOf(Serializable keyspace) {
Cache cache = getCache(keyspace);
if (cache == null) {
return;
}
cache.removeAll();
}
@Override
public void clear() {
cacheManager.clearAll();
}
protected Cache getCache(Serializable collection) {
return getCache(collection, null);
}
protected Cache getCache(Serializable collection, final Class<?> type) {
Assert.notNull(collection, "Collection must not be 'null' for lookup.");
Assert.isInstanceOf(String.class, collection, "Collection identifier must be of type String.");
Class<?> userType = ClassUtils.getUserClass(type);
String collectionName = (String) collection;
if (!cacheManager.cacheExists(collectionName)) {
if (type == null) {
return null;
}
CacheConfiguration cacheConfig = cacheManager.getConfiguration().getDefaultCacheConfiguration().clone();
if (!cacheConfig.isSearchable()) {
cacheConfig = new CacheConfiguration();
cacheConfig.setMaxEntriesLocalHeap(0);
}
cacheConfig.setName(collectionName);
final Searchable s = new Searchable();
// TODO: maybe use mappingcontex information at this point or register generic type using some spel expression
// validator
ReflectionUtils.doWithFields(userType, new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(type, field.getName());
if (pd != null && pd.getReadMethod() != null) {
s.addSearchAttribute(new SearchAttribute().name(field.getName()).expression(
"value." + pd.getReadMethod().getName() + "()"));
}
}
});
cacheConfig.addSearchable(s);
cacheManager.addCache(new Cache(cacheConfig));
}
return cacheManager.getCache(collectionName);
}
private enum ElementConverter implements Converter<Element, Object> {
INSTANCE;
@Override
public Object convert(Element source) {
if (source == null) {
return null;
}
return source.getObjectValue();
}
}
@Override
public void destroy() throws Exception {
this.cacheManager.shutdown();
}
}

153
src/main/java/org/springframework/data/keyvalue/ehcache/EhCacheQueryEngine.java vendored

@ -0,0 +1,153 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Direction;
import net.sf.ehcache.search.Query;
import net.sf.ehcache.search.Result;
import net.sf.ehcache.search.Results;
import net.sf.ehcache.search.expression.Criteria;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.CriteriaAccessor;
import org.springframework.data.keyvalue.core.QueryEngine;
import org.springframework.data.keyvalue.core.SortAccessor;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.util.CollectionUtils;
/**
* @author Christoph Strobl
*/
public class EhCacheQueryEngine extends QueryEngine<EhCacheKeyValueAdapter, Criteria, Map<Attribute<?>, Direction>> {
public EhCacheQueryEngine() {
super(EhCacheCriteriaAccessor.INSTANCE, EhCacheSortAccessor.INSTNANCE);
}
@Override
public Collection<?> execute(Criteria criteria, Map<Attribute<?>, Direction> sort, int offset, int rows,
Serializable keyspace) {
Query cacheQuery = prepareQuery(criteria, sort, keyspace);
if (cacheQuery == null) {
return Collections.emptyList();
}
Results result = cacheQuery.execute();
ListConverter<Result, Object> listConc = new ListConverter<Result, Object>(ResultConverter.INSTANCE);
if (rows > 0 && offset >= 0) {
return listConc.convert(result.range(offset, rows));
}
return listConc.convert(result.all());
}
@Override
public long count(Criteria criteria, Serializable keyspace) {
Query q = prepareQuery(criteria, null, keyspace);
if (q == null) {
return 0;
}
return q.execute().size();
}
private Query prepareQuery(Criteria criteria, Map<Attribute<?>, Direction> sort, Serializable keyspace) {
Cache cache = getAdapter().getCache(keyspace);
if (cache == null) {
return null;
}
Query cacheQuery = cache.createQuery().includeValues();
if (criteria != null) {
cacheQuery.addCriteria(criteria);
}
if (!CollectionUtils.isEmpty(sort)) {
for (Map.Entry<Attribute<?>, Direction> order : sort.entrySet()) {
cacheQuery.addOrderBy(order.getKey(), order.getValue());
}
}
cacheQuery.end();
return cacheQuery;
}
static enum EhCacheCriteriaAccessor implements CriteriaAccessor<Criteria> {
INSTANCE;
@Override
public Criteria resolve(KeyValueQuery<?> query) {
if (query == null || query.getCritieria() == null) {
return null;
}
if (query.getCritieria() instanceof Criteria) {
return (Criteria) query.getCritieria();
}
throw new UnsupportedOperationException();
}
}
static enum EhCacheSortAccessor implements SortAccessor<Map<Attribute<?>, Direction>> {
INSTNANCE;
@SuppressWarnings({ "rawtypes" })
@Override
public Map<Attribute<?>, Direction> resolve(KeyValueQuery<?> query) {
if (query == null || query.getSort() == null) {
return null;
}
Map<Attribute<?>, Direction> attributes = new LinkedHashMap<Attribute<?>, Direction>();
for (Sort.Order order : query.getSort()) {
attributes.put(new Attribute(order.getProperty()), org.springframework.data.domain.Sort.Direction.ASC
.equals(order.getDirection()) ? net.sf.ehcache.search.Direction.ASCENDING
: net.sf.ehcache.search.Direction.DESCENDING);
}
return attributes;
}
}
static enum ResultConverter implements Converter<Result, Object> {
INSTANCE;
@Override
public Object convert(Result source) {
return source.getValue();
}
}
}

51
src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java vendored

@ -0,0 +1,51 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
/**
* @author Christoph Strobl
* @param <S>
* @param <T>
* @since 1.10
*/
public class ListConverter<S, T> implements Converter<Iterable<S>, List<T>> {
private Converter<S, T> itemConverter;
/**
* @param itemConverter The {@link Converter} to use for converting individual List items
*/
public ListConverter(Converter<S, T> itemConverter) {
this.itemConverter = itemConverter;
}
public List<T> convert(Iterable<S> source) {
if (source == null) {
return null;
}
List<T> results = new ArrayList<T>();
for (S result : source) {
results.add(itemConverter.convert(result));
}
return results;
}
}

135
src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java vendored

@ -0,0 +1,135 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache.repository.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.ehcache.repository.query.EhCacheQueryCreator;
import org.springframework.data.keyvalue.repository.config.EnableKeyValueRepositories;
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* Annotation to activate EhCache repositories. If no base package is configured through either {@link #value()},
* {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class.
*
* @author Christoph Strobl
* @since 1.10
*/
@EnableKeyValueRepositories
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(KeyValueRepositoriesRegistrar.class)
public @interface EnableEhCacheRepositories {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
* each package that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* Specifies which types are not eligible for component scanning.
*/
Filter[] excludeFilters() default {};
/**
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
*/
Filter[] includeFilters() default {};
/**
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
* for {@code PersonRepositoryImpl}.
*
* @return
*/
String repositoryImplementationPostfix() default "Impl";
/**
* Configures the location of where to find the Spring Data named queries properties file.
*
* @return
*/
String namedQueriesLocation() default "";
/**
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {@link Key#CREATE_IF_NOT_FOUND}.
*
* @return
*/
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
/**
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
* {@link KeyValueRepositoryFactoryBean}.
*
* @return
*/
Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class;
/**
* Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected.
*
* @return
*/
String keyValueTemplateRef() default "keyValueTemplate";
/**
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
* repositories infrastructure.
*/
boolean considerNestedRepositories() default false;
/**
* Configures the query creator to be used for deriving queries from
* {@link org.springframework.data.repository.query.parser.PartTree}.
*
* @return
*/
Class<? extends AbstractQueryCreator<?, ?>> queryCreator() default EhCacheQueryCreator.class;
}

128
src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java vendored

@ -0,0 +1,128 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache.repository.query;
import java.util.Iterator;
import net.sf.ehcache.search.expression.Criteria;
import net.sf.ehcache.search.expression.EqualTo;
import net.sf.ehcache.search.expression.GreaterThan;
import net.sf.ehcache.search.expression.ILike;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class EhCacheQueryCreator extends AbstractQueryCreator<KeyValueQuery<Criteria>, Criteria> {
/**
* Creates a new {@link EhCacheQueryCreator} for the given {@link PartTree}.
*
* @param tree must not be {@literal null}.
*/
public EhCacheQueryCreator(PartTree tree) {
super(tree);
}
/**
* Creates a new {@link EhCacheQueryCreator} for the given {@link PartTree} and {@link ParameterAccessor}. The latter
* is used to hand actual parameter values into the callback methods as well as to apply dynamic sorting via a
* {@link Sort} parameter.
*
* @param tree must not be {@literal null}.
* @param parameters can be {@literal null}.
*/
public EhCacheQueryCreator(PartTree tree, ParameterAccessor parameters) {
super(tree, parameters);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
*/
@Override
protected Criteria create(Part part, Iterator<Object> iterator) {
return from(part, iterator);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
*/
@Override
protected Criteria and(Part part, Criteria base, Iterator<Object> iterator) {
if (base == null) {
return create(part, iterator);
}
return base.and(from(part, iterator));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
*/
@Override
protected Criteria or(Criteria base, Criteria criteria) {
return base.or(criteria);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
*/
@Override
protected KeyValueQuery<Criteria> complete(Criteria criteria, Sort sort) {
KeyValueQuery<Criteria> query = new KeyValueQuery<Criteria>(criteria);
if (sort != null) {
query.orderBy(sort);
}
return query;
}
private Criteria from(Part part, Iterator<Object> iterator) {
// TODO: complete list of supported types
switch (part.getType()) {
case TRUE:
return new EqualTo(part.getProperty().toDotPath(), true);
case FALSE:
return new EqualTo(part.getProperty().toDotPath(), true);
case SIMPLE_PROPERTY:
return new EqualTo(part.getProperty().toDotPath(), iterator.next());
case IS_NULL:
return new EqualTo(part.getProperty().toDotPath(), null);
case STARTING_WITH:
case LIKE:
return new ILike(part.getProperty().toDotPath(), iterator.next() + "*");
case GREATER_THAN:
return new GreaterThan(part.getProperty().toDotPath(), iterator.next());
default:
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType()));
}
}
}

98
src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastKeyValueAdapter.java

@ -0,0 +1,98 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
import org.springframework.util.Assert;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
/**
* @author Christoph Strobl
*/
public class HazelcastKeyValueAdapter extends AbstractKeyValueAdapter {
private HazelcastInstance hzInstance;
public HazelcastKeyValueAdapter() {
this(Hazelcast.newHazelcastInstance());
}
public HazelcastKeyValueAdapter(HazelcastInstance hzInstance) {
super(new HazelcastQueryEngine());
Assert.notNull(hzInstance, "hzInstance must not be 'null'.");
this.hzInstance = hzInstance;
}
@SuppressWarnings("unchecked")
@Override
public Object put(Serializable id, Object item, Serializable keyspace) {
Assert.notNull(id, "Id must not be 'null' for adding.");
Assert.notNull(item, "Item must not be 'null' for adding.");
return getMap(keyspace).put(id, item);
}
@Override
public boolean contains(Serializable id, Serializable keyspace) {
return getMap(keyspace).containsKey(id);
}
@Override
public Object get(Serializable id, Serializable keyspace) {
return getMap(keyspace).get(id);
}
@Override
public Object delete(Serializable id, Serializable keyspace) {
return getMap(keyspace).remove(id);
}
@Override
public Collection<?> getAllOf(Serializable keyspace) {
return getMap(keyspace).values();
}
@Override
public void deleteAllOf(Serializable keyspace) {
getMap(keyspace).clear();
}
@Override
public void clear() {
// TODO: remove all elements
}
@SuppressWarnings("rawtypes")
protected IMap getMap(final Serializable keyspace) {
Assert.isInstanceOf(String.class, keyspace, "Keyspace identifier must of of type String.");
return hzInstance.getMap((String) keyspace);
}
@Override
public void destroy() throws Exception {
hzInstance.shutdown();
}
}

108
src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java

@ -0,0 +1,108 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast;
import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map.Entry;
import org.springframework.data.keyvalue.core.CriteriaAccessor;
import org.springframework.data.keyvalue.core.QueryEngine;
import org.springframework.data.keyvalue.core.SortAccessor;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import com.hazelcast.query.PagingPredicate;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class HazelcastQueryEngine extends QueryEngine<HazelcastKeyValueAdapter, Predicate<?, ?>, Comparator<Entry>> {
public HazelcastQueryEngine() {
super(HazelcastCriteriaAccessor.INSTANCE, HazelcastSortAccessor.INSTNANCE);
}
@Override
public Collection<?> execute(Predicate<?, ?> criteria, Comparator<Entry> sort, int offset, int rows,
Serializable keyspace) {
Predicate<?, ?> predicateToUse = criteria;
if (sort != null || offset > 0 || rows > 0) {
PagingPredicate pp = new PagingPredicate(criteria, (Comparator<Entry>) sort, rows);
if (offset > 0 && rows > 0) {
int x = offset / rows;
while (x > 0) {
pp.nextPage();
x--;
}
}
predicateToUse = pp;
}
return this.getAdapter().getMap(keyspace).values(predicateToUse);
}
@Override
public long count(Predicate<?, ?> criteria, Serializable keyspace) {
return this.getAdapter().getMap(keyspace).keySet(criteria).size();
}
static enum HazelcastCriteriaAccessor implements CriteriaAccessor<Predicate<?, ?>> {
INSTANCE;
@Override
public Predicate<?, ?> resolve(KeyValueQuery<?> query) {
if (query == null || query.getCritieria() == null) {
return null;
}
if (query.getCritieria() instanceof Predicate) {
return (Predicate<?, ?>) query.getCritieria();
}
if (query.getCritieria() instanceof PredicateBuilder) {
return (PredicateBuilder) query.getCritieria();
}
throw new UnsupportedOperationException();
}
}
static enum HazelcastSortAccessor implements SortAccessor<Comparator<Entry>> {
INSTNANCE;
@Override
public Comparator<Entry> resolve(KeyValueQuery<?> query) {
if (query == null || query.getSort() == null) {
return null;
}
// TODO: create serializable sorter;
throw new UnsupportedOperationException();
}
}
}

135
src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java

@ -0,0 +1,135 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast.repository.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.hazelcast.repository.query.HazelcastQueryCreator;
import org.springframework.data.keyvalue.repository.config.EnableKeyValueRepositories;
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* Annotation to activate Hazelcast repositories. If no base package is configured through either {@link #value()},
* {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class.
*
* @author Christoph Strobl
* @since 1.10
*/
@EnableKeyValueRepositories
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(KeyValueRepositoriesRegistrar.class)
public @interface EnableHazelcastRepositories {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
* each package that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* Specifies which types are not eligible for component scanning.
*/
Filter[] excludeFilters() default {};
/**
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
*/
Filter[] includeFilters() default {};
/**
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
* for {@code PersonRepositoryImpl}.
*
* @return
*/
String repositoryImplementationPostfix() default "Impl";
/**
* Configures the location of where to find the Spring Data named queries properties file.
*
* @return
*/
String namedQueriesLocation() default "";
/**
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {@link Key#CREATE_IF_NOT_FOUND}.
*
* @return
*/
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
/**
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
* {@link KeyValueRepositoryFactoryBean}.
*
* @return
*/
Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class;
/**
* Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected.
*
* @return
*/
String keyValueTemplateRef() default "keyValueTemplate";
/**
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
* repositories infrastructure.
*/
boolean considerNestedRepositories() default false;
/**
* Configures the query creator to be used for deriving queries from
* {@link org.springframework.data.repository.query.parser.PartTree}.
*
* @return
*/
Class<? extends AbstractQueryCreator<?, ?>> queryCreator() default HazelcastQueryCreator.class;
}

121
src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java

@ -0,0 +1,121 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast.repository.query;
import java.util.Iterator;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.keyvalue.ehcache.repository.query.EhCacheQueryCreator;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class HazelcastQueryCreator extends AbstractQueryCreator<KeyValueQuery<Predicate<?, ?>>, Predicate<?, ?>> {
private final PredicateBuilder predicateBuilder;
/**
* Creates a new {@link EhCacheQueryCreator} for the given {@link PartTree}.
*
* @param tree must not be {@literal null}.
*/
public HazelcastQueryCreator(PartTree tree) {
super(tree);
this.predicateBuilder = new PredicateBuilder();
}
/**
* Creates a new {@link HazelcastQueryCreator} for the given {@link PartTree} and {@link ParameterAccessor}. The
* latter is used to hand actual parameter values into the callback methods as well as to apply dynamic sorting via a
* {@link Sort} parameter.
*
* @param tree must not be {@literal null}.
* @param parameters can be {@literal null}.
*/
public HazelcastQueryCreator(PartTree tree, ParameterAccessor parameters) {
super(tree, parameters);
this.predicateBuilder = new PredicateBuilder();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
*/
@Override
protected Predicate<?, ?> create(Part part, Iterator<Object> iterator) {
return from(predicateBuilder, part, iterator);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
*/
@Override
protected Predicate<?, ?> and(Part part, Predicate<?, ?> base, Iterator<Object> iterator) {
return predicateBuilder.and(from(predicateBuilder, part, iterator));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
*/
@Override
protected Predicate<?, ?> or(Predicate<?, ?> base, Predicate<?, ?> criteria) {
return predicateBuilder.or(criteria);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
*/
@Override
protected KeyValueQuery<Predicate<?, ?>> complete(Predicate<?, ?> criteria, Sort sort) {
return new KeyValueQuery<Predicate<?, ?>>(criteria);
}
private Predicate<?, ?> from(PredicateBuilder pb, Part part, Iterator<Object> iterator) {
EntryObject e = pb.getEntryObject();
e.get(part.getProperty().toDotPath());
switch (part.getType()) {
case TRUE:
return e.equal(true);
case FALSE:
return e.equal(false);
case SIMPLE_PROPERTY:
return e.equal((Comparable<?>) iterator.next());
case IS_NULL:
return e.isNull();
case GREATER_THAN:
return e.greaterThan((Comparable<?>) iterator.next());
default:
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType()));
}
}
}

166
src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java

@ -0,0 +1,166 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.CollectionFactory;
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
import org.springframework.data.keyvalue.core.KeyValueAdapter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link KeyValueAdapter} implementation for {@link Map}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class MapKeyValueAdapter extends AbstractKeyValueAdapter {
private final Map<Serializable, Map<Serializable, Object>> data;
@SuppressWarnings("rawtypes")//
private final Class<? extends Map> mapType;
/**
* Create new instance of {@link MapKeyValueAdapter} using {@link ConcurrentHashMap}.
*/
public MapKeyValueAdapter() {
this(new ConcurrentHashMap<Serializable, Map<Serializable, Object>>());
}
/**
* Create new instance of {@link MapKeyValueAdapter} using given dataStore for persistence.
*
* @param dataStore must not be {@literal null}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public MapKeyValueAdapter(Map<Serializable, Map<Serializable, Object>> dataStore) {
Assert.notNull(dataStore, "Cannot initilalize adapter with 'null' datastore.");
this.data = dataStore;
this.mapType = (Class<? extends Map>) ClassUtils.getUserClass(dataStore);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#put(java.io.Serializable, java.lang.Object, java.io.Serializable)
*/
@Override
public Object put(Serializable id, Object item, Serializable keyspace) {
Assert.notNull(id, "Cannot add item with 'null' id.");
Assert.notNull(keyspace, "Cannot add item for 'null' collection.");
return getKeySpaceMap(keyspace).put(id, item);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#contains(java.io.Serializable, java.io.Serializable)
*/
@Override
public boolean contains(Serializable id, Serializable keyspace) {
return get(id, keyspace) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#get(java.io.Serializable, java.io.Serializable)
*/
@Override
public Object get(Serializable id, Serializable keyspace) {
Assert.notNull(id, "Cannot get item with 'null' id.");
return getKeySpaceMap(keyspace).get(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#delete(java.io.Serializable, java.io.Serializable)
*/
@Override
public Object delete(Serializable id, Serializable keyspace) {
Assert.notNull(id, "Cannot delete item with 'null' id.");
return getKeySpaceMap(keyspace).remove(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#getAllOf(java.io.Serializable)
*/
@Override
public Collection<?> getAllOf(Serializable keyspace) {
return getKeySpaceMap(keyspace).values();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#deleteAllOf(java.io.Serializable)
*/
@Override
public void deleteAllOf(Serializable keyspace) {
getKeySpaceMap(keyspace).clear();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#clear()
*/
@Override
public void clear() {
data.clear();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
clear();
}
/**
* Get map associated with given keyspace.
*
* @param keyspace must not be {@literal null}.
* @return
*/
protected Map<Serializable, Object> getKeySpaceMap(Serializable keyspace) {
Assert.notNull(keyspace, "Collection must not be 'null' for lookup.");
Map<Serializable, Object> map = data.get(keyspace);
if (map != null) {
return map;
}
addMapForKeySpace(keyspace);
return data.get(keyspace);
}
private void addMapForKeySpace(Serializable keyspace) {
data.put(keyspace, CollectionFactory.<Serializable, Object> createMap(mapType, 1000));
}
}

107
src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java

@ -0,0 +1,107 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.CollectionFactory;
import org.springframework.util.Assert;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class MapKeyValueAdapterFactory {
@SuppressWarnings("rawtypes")//
private static final Class<? extends Map> DEFAULT_MAP_TYPE = ConcurrentHashMap.class;
@SuppressWarnings("rawtypes")//
private Class<? extends Map> mapType;
private Map<Serializable, Map<? extends Serializable, ?>> initialValues;
/**
* @see MapKeyValueAdapterFactory#MapKeyValueAdapterFactory(Class)
*/
public MapKeyValueAdapterFactory() {
this(null);
}
/**
* @param type any {@link Class} of type {@link Map}. Can be {@literal null} and will be defaulted to
* {@link ConcurrentHashMap}.
*/
@SuppressWarnings("rawtypes")
public MapKeyValueAdapterFactory(Class<? extends Map> type) {
this.mapType = type;
this.initialValues = new HashMap<Serializable, Map<? extends Serializable, ?>>();
}
/**
* Set values for a given {@literal keyspace} that to populate the adapter after creation.
*
* @param keyspace
* @param values
*/
public void setInitialValuesForKeyspace(Serializable keyspace, Map<? extends Serializable, ?> values) {
Assert.notNull(keyspace, "KeySpace must not be 'null'.");
Assert.notNull(values, "Values must not be 'null'.");
initialValues.put(keyspace, values);
}
@SuppressWarnings("rawtypes")
public void setMapType(Class<? extends Map> mapType) {
this.mapType = mapType;
}
/**
* Creates and populates the adapter.
*
* @return
*/
public MapKeyValueAdapter getAdapter() {
MapKeyValueAdapter adapter = createAdapter();
populateAdapter(adapter);
return adapter;
}
private MapKeyValueAdapter createAdapter() {
Class<?> type = this.mapType == null ? DEFAULT_MAP_TYPE : this.mapType;
MapKeyValueAdapter adapter = new MapKeyValueAdapter(
CollectionFactory.<Serializable, Map<Serializable, Object>> createMap(type, 100));
return adapter;
}
private void populateAdapter(MapKeyValueAdapter adapter) {
if (!initialValues.isEmpty()) {
for (Entry<Serializable, Map<? extends Serializable, ?>> entry : initialValues.entrySet()) {
for (Entry<? extends Serializable, ?> obj : entry.getValue().entrySet()) {
adapter.put(obj.getKey(), obj.getValue(), entry.getKey());
}
}
}
}
}

200
src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java

@ -0,0 +1,200 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
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.keyvalue.core.KeyValueOperations;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.util.Assert;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public class BasicKeyValueRepository<T, ID extends Serializable> implements KeyValueRepository<T, ID> {
private final KeyValueOperations operations;
private final EntityInformation<T, ID> entityInformation;
public BasicKeyValueRepository(EntityInformation<T, ID> metadata, KeyValueOperations operations) {
Assert.notNull(metadata, "Cannot initialize repository for 'null' metadata");
Assert.notNull(operations, "Cannot initialize repository for 'null' operations");
this.entityInformation = metadata;
this.operations = operations;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<T> findAll(Sort sort) {
return operations.findAll(sort, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Pageable pageable) {
List<T> content = null;
if (pageable.getSort() != null) {
content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), pageable.getSort(),
entityInformation.getJavaType());
} else {
content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), entityInformation.getJavaType());
}
return new PageImpl<T>(content, pageable, this.operations.count(entityInformation.getJavaType()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be 'null' for save.");
if (entityInformation.isNew(entity)) {
operations.insert(entity);
} else {
operations.update(entityInformation.getId(entity), entity);
}
return entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
for (S entity : entities) {
save(entity);
}
return entities;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
*/
@Override
public T findOne(ID id) {
return operations.findById(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
*/
@Override
public boolean exists(ID id) {
return findOne(id) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
public List<T> findAll() {
return operations.findAll(entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
public Iterable<T> findAll(Iterable<ID> ids) {
List<T> result = new ArrayList<T>();
for (ID id : ids) {
T candidate = findOne(id);
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
*/
@Override
public long count() {
return operations.count(entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Override
public void delete(ID id) {
operations.delete(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Override
public void delete(T entity) {
delete(entityInformation.getId(entity));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Override
public void delete(Iterable<? extends T> entities) {
for (T entity : entities) {
delete(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#deleteAll()
*/
@Override
public void deleteAll() {
operations.delete(entityInformation.getJavaType());
}
}

30
src/main/java/org/springframework/data/keyvalue/repository/KeyValueRepository.java

@ -0,0 +1,30 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository;
import java.io.Serializable;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public interface KeyValueRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
}

156
src/main/java/org/springframework/data/keyvalue/repository/QueryDslKeyValueRepository.java

@ -0,0 +1,156 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.keyvalue.core.KeyValueOperations;
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 com.mysema.query.collections.CollQuery;
import com.mysema.query.support.ProjectableQuery;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.path.PathBuilder;
/**
* {@link KeyValueRepository} implementation capable of executing {@link Predicate}s using {@link CollQuery}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public class QueryDslKeyValueRepository<T, ID extends Serializable> extends BasicKeyValueRepository<T, ID> implements
QueryDslPredicateExecutor<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
/**
* @param entityInformation
* @param operations
*/
public QueryDslKeyValueRepository(EntityInformation<T, ID> entityInformation, KeyValueOperations operations) {
this(entityInformation, operations, DEFAULT_ENTITY_PATH_RESOLVER);
}
/**
* @param entityInformation
* @param operations
* @param resolver
*/
public QueryDslKeyValueRepository(EntityInformation<T, ID> entityInformation, KeyValueOperations operations,
EntityPathResolver resolver) {
super(entityInformation, operations);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findOne(com.mysema.query.types.Predicate)
*/
@Override
public T findOne(Predicate predicate) {
ProjectableQuery<?> query = prepareQuery(predicate);
return query.uniqueResult(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate)
*/
@Override
public Iterable<T> findAll(Predicate predicate) {
ProjectableQuery<?> query = prepareQuery(predicate);
return query.list(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, com.mysema.query.types.OrderSpecifier[])
*/
@Override
public Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
ProjectableQuery<?> query = prepareQuery(predicate);
query.orderBy(orders);
return query.list(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
ProjectableQuery<?> query = prepareQuery(predicate);
if (pageable != null) {
query.offset(pageable.getOffset());
query.limit(pageable.getPageSize());
if (pageable.getSort() != null) {
query.orderBy(toOrderSpecifier(pageable.getSort(), builder));
}
}
return new PageImpl<T>(query.list(builder), pageable, count(predicate));
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
*/
@Override
public long count(Predicate predicate) {
ProjectableQuery<?> query = prepareQuery(predicate);
return query.count();
}
/**
* Creates executable query for given {@link Predicate}.
*
* @param predicate
* @return
*/
protected ProjectableQuery<?> prepareQuery(Predicate predicate) {
CollQuery query = new CollQuery();
query.from(builder, findAll());
query.where(predicate);
return query;
}
}

132
src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java

@ -0,0 +1,132 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* Annotation to activate KeyValue repositories. If no base package is configured through either {@link #value()},
* {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class.
*
* @author Christoph Strobl
* @since 1.10
*/
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(KeyValueRepositoriesRegistrar.class)
public @interface EnableKeyValueRepositories {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
* each package that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* Specifies which types are not eligible for component scanning.
*/
Filter[] excludeFilters() default {};
/**
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
*/
Filter[] includeFilters() default {};
/**
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
* for {@code PersonRepositoryImpl}.
*
* @return
*/
String repositoryImplementationPostfix() default "Impl";
/**
* Configures the location of where to find the Spring Data named queries properties file.
*
* @return
*/
String namedQueriesLocation() default "";
/**
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {@link Key#CREATE_IF_NOT_FOUND}.
*
* @return
*/
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
/**
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
* {@link KeyValueRepositoryFactoryBean}.
*
* @return
*/
Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class;
/**
* Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected.
*
* @return
*/
String keyValueTemplateRef() default "keyValueTemplate";
/**
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
* repositories infrastructure.
*/
boolean considerNestedRepositories() default false;
/**
* Configures the query creator to be used for deriving queries from
* {@link org.springframework.data.repository.query.parser.PartTree}.
*
* @return
*/
Class<? extends AbstractQueryCreator<?, ?>> queryCreator() default SpelQueryCreator.class;
}

49
src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java

@ -0,0 +1,49 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.config;
import java.lang.annotation.Annotation;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
/**
* KeyValue specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableKeyValueRepositories.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/
@Override
protected RepositoryConfigurationExtension getExtension() {
return new KeyValueRepositoryConfigurationExtension();
}
}

104
src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java

@ -0,0 +1,104 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.config;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
/**
* {@link RepositoryConfigurationExtension} for {@link KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
private boolean mappingContextAvailable = false;
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryClassName()
*/
@Override
public String getRepositoryFactoryClassName() {
return KeyValueRepositoryFactoryBean.class.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName()
*/
@Override
public String getModuleName() {
return "KeyValue";
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix()
*/
@Override
protected String getModulePrefix() {
return "keyvalue";
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes()
*/
@Override
protected Collection<Class<?>> getIdentifyingTypes() {
return Collections.<Class<?>> singleton(KeyValueRepository.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
*/
@Override
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
AnnotationAttributes attributes = config.getAttributes();
builder.addPropertyReference("keyValueOperations", attributes.getString("keyValueTemplateRef"));
builder.addPropertyValue("queryCreator", attributes.getClass("queryCreator"));
if (mappingContextAvailable) {
builder.addPropertyReference("mappingContext", "keyValueMappingContext");
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
*/
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
super.registerBeansForRoot(registry, configurationSource);
this.mappingContextAvailable = registry.containsBeanDefinition("keyValueMappingContext");
}
}

179
src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java

@ -0,0 +1,179 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.query;
import java.util.Iterator;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.repository.query.parser.PartTree.OrPart;
import org.springframework.expression.spel.standard.SpelExpression;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExpression>, String> {
private SpelExpression expression;
public SpelQueryCreator(PartTree tree, ParameterAccessor parameters) {
super(tree, parameters);
this.expression = toPredicateExpression(tree);
}
@Override
protected String create(Part part, Iterator<Object> iterator) {
return "";
}
@Override
protected String and(Part part, String base, Iterator<Object> iterator) {
return "";
}
@Override
protected String or(String base, String criteria) {
return "";
}
@Override
protected KeyValueQuery<SpelExpression> complete(String criteria, Sort sort) {
KeyValueQuery<SpelExpression> query = new KeyValueQuery<SpelExpression>(this.expression);
if (sort != null) {
query.orderBy(sort);
}
return query;
}
protected SpelExpression toPredicateExpression(PartTree tree) {
int parameterIndex = 0;
StringBuilder sb = new StringBuilder();
for (Iterator<OrPart> orPartIter = tree.iterator(); orPartIter.hasNext();) {
OrPart orPart = orPartIter.next();
int partCnt = 0;
StringBuilder partBuilder = new StringBuilder();
for (Iterator<Part> partIter = orPart.iterator(); partIter.hasNext();) {
Part part = partIter.next();
partBuilder.append("#it?.");
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
// TODO: check if we can have caseinsensitive search
if (!part.shouldIgnoreCase().equals(IgnoreCaseType.NEVER)) {
throw new InvalidDataAccessApiUsageException("Ignore case not supported");
}
switch (part.getType()) {
case TRUE:
partBuilder.append("?.equals(true)");
break;
case FALSE:
partBuilder.append("?.equals(false)");
break;
case SIMPLE_PROPERTY:
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("])");
break;
case IS_NULL:
partBuilder.append(" == null");
break;
case IS_NOT_NULL:
partBuilder.append(" != null");
break;
case LIKE:
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("])");
break;
case STARTING_WITH:
partBuilder.append("?.startsWith(").append("[").append(parameterIndex++).append("])");
break;
case AFTER:
case GREATER_THAN:
partBuilder.append(">").append("[").append(parameterIndex++).append("]");
break;
case GREATER_THAN_EQUAL:
partBuilder.append(">=").append("[").append(parameterIndex++).append("]");
break;
case BEFORE:
case LESS_THAN:
partBuilder.append("<").append("[").append(parameterIndex++).append("]");
break;
case LESS_THAN_EQUAL:
partBuilder.append("<=").append("[").append(parameterIndex++).append("]");
break;
case ENDING_WITH:
partBuilder.append("?.endsWith(").append("[").append(parameterIndex++).append("])");
break;
case BETWEEN:
int index = partBuilder.lastIndexOf("#it?.");
partBuilder.insert(index, "(");
partBuilder.append(">").append("[").append(parameterIndex++).append("]");
partBuilder.append("&&");
partBuilder.append("#it?.");
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
partBuilder.append("<").append("[").append(parameterIndex++).append("]");
partBuilder.append(")");
break;
case REGEX:
partBuilder.append(" matches ").append("[").append(parameterIndex++).append("]");
break;
case IN:
case CONTAINING:
case NOT_CONTAINING:
case NEGATING_SIMPLE_PROPERTY:
case EXISTS:
default:
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query",
part.getType()));
}
if (partIter.hasNext()) {
partBuilder.append("&&");
}
partCnt++;
}
if (partCnt > 1) {
sb.append("(").append(partBuilder).append(")");
} else {
sb.append(partBuilder);
}
if (orPartIter.hasNext()) {
sb.append("||");
}
}
return SpelExpressionFactory.parseRaw(sb.toString());
}
}

288
src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java

@ -0,0 +1,288 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.support;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.keyvalue.repository.BasicKeyValueRepository;
import org.springframework.data.keyvalue.repository.QueryDslKeyValueRepository;
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryLookupStrategy;
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.data.repository.query.parser.PartTree;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* {@link RepositoryFactorySupport} specific of handing
* {@link org.springframework.data.keyvalue.repository.KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoryFactory extends RepositoryFactorySupport {
private static final Class<SpelQueryCreator> DEFAULT_QUERY_CREATOR = SpelQueryCreator.class;
private final KeyValueOperations keyValueOperations;
private final MappingContext<?, ?> context;
private final Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
/**
* @param keyValueOperations must not be {@literal null}.
*/
public KeyValueRepositoryFactory(KeyValueOperations keyValueOperations) {
this(keyValueOperations, DEFAULT_QUERY_CREATOR);
}
/**
* @param keyValueOperations must not be {@literal null}.
* @param queryCreator defaulted to {@link #DEFAULT_QUERY_CREATOR} if {@literal null}.
*/
public KeyValueRepositoryFactory(KeyValueOperations keyValueOperations,
Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
Assert.notNull(keyValueOperations, "KeyValueOperations must not be 'null' when creating factory.");
this.queryCreator = queryCreator != null ? queryCreator : DEFAULT_QUERY_CREATOR;
this.keyValueOperations = keyValueOperations;
this.context = keyValueOperations.getMappingContext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
PersistentEntity<T, ?> entity = (PersistentEntity<T, ?>) context.getPersistentEntity(domainClass);
PersistentEntityInformation<T, ID> entityInformation = new PersistentEntityInformation<T, ID>(entity);
return entityInformation;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected Object getTargetRepository(RepositoryMetadata metadata) {
EntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
if (ClassUtils.isAssignable(QueryDslPredicateExecutor.class, metadata.getRepositoryInterface())) {
return new QueryDslKeyValueRepository(entityInformation, keyValueOperations);
}
return new BasicKeyValueRepository(entityInformation, keyValueOperations);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return isQueryDslRepository(metadata.getRepositoryInterface()) ? QueryDslKeyValueRepository.class
: BasicKeyValueRepository.class;
}
/**
* Returns whether the given repository interface requires a QueryDsl specific implementation to be chosen.
*
* @param repositoryInterface
* @return
*/
private static boolean isQueryDslRepository(Class<?> repositoryInterface) {
return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) {
return new KeyValueQueryLookupStrategy(key, evaluationContextProvider, this.keyValueOperations, this.queryCreator);
}
/**
* @author Christoph Strobl
* @since 1.10
*/
static class KeyValueQueryLookupStrategy implements QueryLookupStrategy {
private EvaluationContextProvider evaluationContextProvider;
private KeyValueOperations keyValueOperations;
private Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
public KeyValueQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider,
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
this.evaluationContextProvider = evaluationContextProvider;
this.keyValueOperations = keyValueOperations;
this.queryCreator = queryCreator;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
*/
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
QueryMethod queryMethod = new QueryMethod(method, metadata);
return doResolveQuery(queryMethod, evaluationContextProvider, this.keyValueOperations);
}
protected RepositoryQuery doResolveQuery(QueryMethod queryMethod, EvaluationContextProvider contextProvider,
KeyValueOperations keyValueOps) {
return new KeyValuePartTreeQuery(queryMethod, evaluationContextProvider, this.keyValueOperations,
this.queryCreator);
}
}
/**
* {@link RepositoryQuery} implementation deriving queries from {@link PartTree} using a predefined
* {@link AbstractQueryCreator}.
*
* @author Christoph Strobl
* @since 1.10
*/
public static class KeyValuePartTreeQuery implements RepositoryQuery {
private EvaluationContextProvider evaluationContextProvider;
private final QueryMethod queryMethod;
private final KeyValueOperations keyValueOperations;
private KeyValueQuery<?> query;
private Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider,
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
this.queryMethod = queryMethod;
this.keyValueOperations = keyValueOperations;
this.evaluationContextProvider = evalContextProvider;
this.queryCreator = queryCreator;
}
@Override
public QueryMethod getQueryMethod() {
return queryMethod;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Object execute(Object[] parameters) {
KeyValueQuery<?> query = prepareQuery(parameters);
if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) {
Pageable page = (Pageable) parameters[queryMethod.getParameters().getPageableIndex()];
query.setOffset(page.getOffset());
query.setRows(page.getPageSize());
List<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
long count = queryMethod.isSliceQuery() ? 0 : keyValueOperations.count(query, queryMethod
.getEntityInformation().getJavaType());
return new PageImpl(result, page, count);
}
if (queryMethod.isCollectionQuery()) {
return this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
}
if (queryMethod.isQueryForEntity()) {
List<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
return CollectionUtils.isEmpty(result) ? null : result.get(0);
}
throw new UnsupportedOperationException("Query method not supported.");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private KeyValueQuery<?> prepareQuery(Object[] parameters) {
ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(),
parameters);
if (this.query == null) {
this.query = createQuery(accessor);
}
KeyValueQuery<?> q = new KeyValueQuery(this.query.getCritieria());
if (accessor.getPageable() != null) {
q.setOffset(accessor.getPageable().getOffset());
q.setRows(accessor.getPageable().getPageSize());
} else {
q.setOffset(-1);
q.setRows(-1);
}
if (accessor.getSort() != null) {
q.setSort(accessor.getSort());
} else {
q.setSort(this.query.getSort());
}
if (q.getCritieria() instanceof SpelExpression) {
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod()
.getParameters(), parameters);
((SpelExpression) q.getCritieria()).setEvaluationContext(context);
}
return q;
}
public KeyValueQuery<?> createQuery(ParametersParameterAccessor accessor) {
PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType());
Constructor<? extends AbstractQueryCreator<?, ?>> constructor = (Constructor<? extends AbstractQueryCreator<?, ?>>) ClassUtils
.getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class);
return (KeyValueQuery<?>) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery();
}
}
}

83
src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java

@ -0,0 +1,83 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.support;
import java.io.Serializable;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* {@link org.springframework.beans.factory.FactoryBean} to create {@link KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
RepositoryFactoryBeanSupport<T, S, ID> {
private KeyValueOperations operations;
private boolean mappingContextAvailable = false;
private Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
public void setKeyValueOperations(KeyValueOperations operations) {
this.operations = operations;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setMappingContext(org.springframework.data.mapping.context.MappingContext)
*/
@Override
public void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
this.mappingContextAvailable = mappingContext != null;
}
public void setQueryCreator(Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
this.queryCreator = queryCreator;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createRepositoryFactory()
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new KeyValueRepositoryFactory(this.operations, this.queryCreator);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
if (!mappingContextAvailable) {
super.setMappingContext(operations.getMappingContext());
}
super.afterPropertiesSet();
}
}

108
src/main/java/org/springframework/data/querydsl/QueryDslUtils.java

@ -15,10 +15,26 @@
*/ */
package org.springframework.data.querydsl; package org.springframework.data.querydsl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
import com.mysema.query.support.Expressions;
import com.mysema.query.types.Expression;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.OrderSpecifier.NullHandling;
import com.mysema.query.types.Path;
import com.mysema.query.types.path.PathBuilder;
/** /**
* Utility class for Querydsl. * Utility class for Querydsl.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
*/ */
public abstract class QueryDslUtils { public abstract class QueryDslUtils {
@ -28,4 +44,96 @@ public abstract class QueryDslUtils {
private QueryDslUtils() { private QueryDslUtils() {
} }
/**
* Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}.
*
* @param sort
* @param builder must not be {@literal null}.
* @return empty {@code OrderSpecifier<?>[]} when sort is {@literal null}.
*/
public static OrderSpecifier<?>[] toOrderSpecifier(Sort sort, PathBuilder<?> builder) {
Assert.notNull(builder, "Builder must not be 'null'.");
if (sort == null) {
return new OrderSpecifier<?>[0];
}
List<OrderSpecifier<?>> specifiers = null;
if (sort instanceof QSort) {
specifiers = ((QSort) sort).getOrderSpecifiers();
} else {
specifiers = new ArrayList<OrderSpecifier<?>>();
for (Order order : sort) {
specifiers.add(toOrderSpecifier(order, builder));
}
}
return specifiers.toArray(new OrderSpecifier<?>[specifiers.size()]);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static OrderSpecifier<?> toOrderSpecifier(Order order, PathBuilder<?> builder) {
return new OrderSpecifier(order.isAscending() ? com.mysema.query.types.Order.ASC
: com.mysema.query.types.Order.DESC, buildOrderPropertyPathFrom(order, builder),
toQueryDslNullHandling(order.getNullHandling()));
}
/**
* Creates an {@link Expression} for the given {@link Order} property.
*
* @param order must not be {@literal null}.
* @param builder must not be {@literal null}.
* @return
*/
private static Expression<?> buildOrderPropertyPathFrom(Order order, PathBuilder<?> builder) {
Assert.notNull(order, "Order must not be null!");
Assert.notNull(builder, "Builder must not be null!");
PropertyPath path = PropertyPath.from(order.getProperty(), builder.getType());
Expression<?> sortPropertyExpression = builder;
while (path != null) {
if (!path.hasNext() && order.isIgnoreCase()) {
// if order is ignore-case we have to treat the last path segment as a String.
sortPropertyExpression = Expressions.stringPath((Path<?>) sortPropertyExpression, path.getSegment()).lower();
} else {
sortPropertyExpression = Expressions.path(path.getType(), (Path<?>) sortPropertyExpression, path.getSegment());
}
path = path.next();
}
return sortPropertyExpression;
}
/**
* Converts the given {@link org.springframework.data.domain.Sort.NullHandling} to the appropriate Querydsl
* {@link NullHandling}.
*
* @param nullHandling must not be {@literal null}.
* @return
*/
private static NullHandling toQueryDslNullHandling(org.springframework.data.domain.Sort.NullHandling nullHandling) {
Assert.notNull(nullHandling, "NullHandling must not be null!");
switch (nullHandling) {
case NULLS_FIRST:
return NullHandling.NullsFirst;
case NULLS_LAST:
return NullHandling.NullsLast;
case NATIVE:
default:
return NullHandling.Default;
}
}
} }

7
src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java

@ -18,11 +18,13 @@ package org.springframework.data.repository.config;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandler; import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension;
/** /**
* {@link NamespaceHandler} to register {@link BeanDefinitionParser}s for repository initializers. * {@link NamespaceHandler} to register {@link BeanDefinitionParser}s for repository initializers.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
* @since 1.4 * @since 1.4
*/ */
public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport { public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport {
@ -34,8 +36,13 @@ public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport {
* @see org.springframework.beans.factory.xml.NamespaceHandler#init() * @see org.springframework.beans.factory.xml.NamespaceHandler#init()
*/ */
public void init() { public void init() {
registerBeanDefinitionParser("unmarshaller-populator", PARSER); registerBeanDefinitionParser("unmarshaller-populator", PARSER);
registerBeanDefinitionParser("jackson-populator", PARSER); registerBeanDefinitionParser("jackson-populator", PARSER);
registerBeanDefinitionParser("jackson2-populator", PARSER); registerBeanDefinitionParser("jackson2-populator", PARSER);
KeyValueRepositoryConfigurationExtension extension = new KeyValueRepositoryConfigurationExtension();
RepositoryBeanDefinitionParser repositoryBeanDefinitionParser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", repositoryBeanDefinitionParser);
} }
} }

9
src/main/java/org/springframework/data/repository/query/DefaultEvaluationContextProvider.java

@ -17,12 +17,14 @@ package org.springframework.data.repository.query;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
/** /**
* Default implementation of {@link EvaluationContextProvider} that always creates a new {@link EvaluationContext}. * Default implementation of {@link EvaluationContextProvider} that always creates a new {@link EvaluationContext}.
* *
* @author Thomas Darimont * @author Thomas Darimont
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
* @since 1.9 * @since 1.9
*/ */
public enum DefaultEvaluationContextProvider implements EvaluationContextProvider { public enum DefaultEvaluationContextProvider implements EvaluationContextProvider {
@ -34,8 +36,9 @@ public enum DefaultEvaluationContextProvider implements EvaluationContextProvide
* @see org.springframework.data.repository.query.EvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[]) * @see org.springframework.data.repository.query.EvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[])
*/ */
@Override @Override
public <T extends Parameters<T, ? extends Parameter>> EvaluationContext getEvaluationContext(T parameters, public <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues) {
Object[] parameterValues) {
return new StandardEvaluationContext(); return ObjectUtils.isEmpty(parameterValues) ? new StandardEvaluationContext() : new StandardEvaluationContext(
parameterValues);
} }
} }

4
src/main/java/org/springframework/data/repository/query/EvaluationContextProvider.java

@ -23,6 +23,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
* *
* @author Thomas Darimont * @author Thomas Darimont
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
* @since 1.9 * @since 1.9
*/ */
public interface EvaluationContextProvider { public interface EvaluationContextProvider {
@ -34,6 +35,5 @@ public interface EvaluationContextProvider {
* @param parameterValues the values for the parameters. * @param parameterValues the values for the parameters.
* @return * @return
*/ */
<T extends Parameters<T, ? extends Parameter>> EvaluationContext getEvaluationContext(T parameters, <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues);
Object[] parameterValues);
} }

6
src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java

@ -53,6 +53,7 @@ import org.springframework.util.StringUtils;
* *
* @author Thomas Darimont * @author Thomas Darimont
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
* @since 1.9 * @since 1.9
*/ */
public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider, ApplicationContextAware { public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider, ApplicationContextAware {
@ -94,7 +95,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
* @see org.springframework.data.jpa.repository.support.EvaluationContextProvider#getEvaluationContext() * @see org.springframework.data.jpa.repository.support.EvaluationContextProvider#getEvaluationContext()
*/ */
@Override @Override
public <T extends Parameters<T, ? extends Parameter>> StandardEvaluationContext getEvaluationContext(T parameters, public <T extends Parameters<?, ?>> StandardEvaluationContext getEvaluationContext(T parameters,
Object[] parameterValues) { Object[] parameterValues) {
StandardEvaluationContext ec = new StandardEvaluationContext(); StandardEvaluationContext ec = new StandardEvaluationContext();
@ -124,8 +125,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
* @param arguments must not be {@literal null}. * @param arguments must not be {@literal null}.
* @return * @return
*/ */
private <T extends Parameters<T, ? extends Parameter>> Map<String, Object> collectVariables(T parameters, private <T extends Parameters<?, ?>> Map<String, Object> collectVariables(T parameters, Object[] arguments) {
Object[] arguments) {
Map<String, Object> variables = new HashMap<String, Object>(); Map<String, Object> variables = new HashMap<String, Object>();

104
src/test/java/org/springframework/data/keyvalue/Person.java

@ -0,0 +1,104 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue;
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.util.ObjectUtils;
import com.mysema.query.annotations.QueryEntity;
/**
* @author Christoph Strobl
*/
@QueryEntity
public class Person implements Serializable {
private @Id String id;
private String firstname;
private int age;
public Person(String firstname, int age) {
super();
this.firstname = firstname;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
@Override
public String toString() {
return "Person [id=" + id + ", firstname=" + firstname + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ObjectUtils.nullSafeHashCode(this.firstname);
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.firstname, other.firstname)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.age, other.age)) {
return false;
}
return true;
}
}

468
src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java

@ -0,0 +1,468 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsEmptyCollection.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsSame.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.keyvalue.map.MapKeyValueAdapter;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class KeyValueTemplateTests {
private static final Foo FOO_ONE = new Foo("one");
private static final Foo FOO_TWO = new Foo("two");
private static final Foo FOO_THREE = new Foo("three");
private static final Bar BAR_ONE = new Bar("one");
private static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super");
private static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub");
private static final KeyValueQuery<String> STRING_QUERY = new KeyValueQuery<String>("foo == 'two'");
private KeyValueTemplate operations;
@Before
public void setUp() throws InstantiationException, IllegalAccessException {
this.operations = new KeyValueTemplate(new MapKeyValueAdapter());
}
@After
public void tearDown() throws Exception {
this.operations.destroy();
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldNotThorwErrorWhenExecutedHavingNonExistingIdAndNonNullValue() {
operations.insert("1", FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullId() {
operations.insert(null, FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullObject() {
operations.insert("some-id", null);
}
/**
* @see DATACMNS-525
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void insertShouldThrowExecptionWhenObjectOfSameTypeAlreadyExists() {
operations.insert("1", FOO_ONE);
operations.insert("1", FOO_TWO);
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldWorkCorrectlyWhenObjectsOfDifferentTypesWithSameIdAreInserted() {
operations.insert("1", FOO_ONE);
operations.insert("1", BAR_ONE);
}
/**
* @see DATACMNS-525
*/
@Test
public void createShouldReturnSameInstanceGenerateId() {
ClassWithStringId source = new ClassWithStringId();
ClassWithStringId target = operations.insert(source);
assertThat(target, sameInstance(source));
}
/**
* @see DATACMNS-525
*/
@Test
public void createShouldRespectExistingId() {
ClassWithStringId source = new ClassWithStringId();
source.id = "one";
operations.insert(source);
assertThat(operations.findById("one", ClassWithStringId.class), is(source));
}
/**
* @see DATACMNS-525
*/
@Test
public void findByIdShouldReturnObjectWithMatchingIdAndType() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
/**
* @see DATACMNS-525
*/
@Test
public void findByIdSouldReturnNullIfNoMatchingIdFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("2", Foo.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void findByIdShouldReturnNullIfNoMatchingTypeFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Bar.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void findShouldExecuteQueryCorrectly() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
List<Foo> result = (List<Foo>) operations.find(STRING_QUERY, Foo.class);
assertThat(result, hasSize(1));
assertThat(result.get(0), is(FOO_TWO));
}
/**
* @see DATACMNS-525
*/
@Test
public void readShouldReturnEmptyCollectionIfOffsetOutOfRange() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
operations.insert("3", FOO_THREE);
assertThat(operations.findInRange(5, 5, Foo.class), empty());
}
/**
* @see DATACMNS-525
*/
@Test
public void updateShouldReplaceExistingObject() {
operations.insert("1", FOO_ONE);
operations.update("1", FOO_TWO);
assertThat(operations.findById("1", Foo.class), is(FOO_TWO));
}
/**
* @see DATACMNS-525
*/
@Test
public void updateShouldRespectTypeInformation() {
operations.insert("1", FOO_ONE);
operations.update("1", BAR_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteShouldRemoveObjectCorrectly() {
operations.insert("1", FOO_ONE);
operations.delete("1", Foo.class);
assertThat(operations.findById("1", Foo.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteReturnsNullWhenNotExisting() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("2", Foo.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteReturnsRemovedObject() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("1", Foo.class), is(FOO_ONE));
}
/**
* @see DATACMNS-525
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void deleteThrowsExceptionWhenIdCannotBeExctracted() {
operations.delete(FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test
public void countShouldReturnZeroWhenNoElementsPresent() {
assertThat(operations.count(Foo.class), is(0L));
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldRespectTypeAlias() {
operations.insert("1", ALIASED);
operations.insert("2", SUBCLASS_OF_ALIASED);
assertThat(operations.findAll(ALIASED.getClass()), containsInAnyOrder(ALIASED, SUBCLASS_OF_ALIASED));
}
static class Foo implements Serializable {
private static final long serialVersionUID = -8912754229220128922L;
String foo;
public Foo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.foo);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Foo)) {
return false;
}
Foo other = (Foo) obj;
return ObjectUtils.nullSafeEquals(this.foo, other.foo);
}
}
static class Bar implements Serializable {
private static final long serialVersionUID = 196011921826060210L;
String bar;
public Bar(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.bar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Bar)) {
return false;
}
Bar other = (Bar) obj;
return ObjectUtils.nullSafeEquals(this.bar, other.bar);
}
}
static class ClassWithStringId implements Serializable {
private static final long serialVersionUID = -7481030649267602830L;
@Id String id;
String value;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.value);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithStringId)) {
return false;
}
ClassWithStringId other = (ClassWithStringId) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.value, other.value)) {
return false;
}
return true;
}
}
@ExplicitKeySpace(name = "aliased")
static class ClassWithTypeAlias implements Serializable {
private static final long serialVersionUID = -5921943364908784571L;
@Id String id;
String name;
public ClassWithTypeAlias(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithTypeAlias)) {
return false;
}
ClassWithTypeAlias other = (ClassWithTypeAlias) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.name, other.name)) {
return false;
}
return true;
}
}
static class SubclassOfAliasedType extends ClassWithTypeAlias {
private static final long serialVersionUID = -468809596668871479L;
public SubclassOfAliasedType(String name) {
super(name);
}
}
@Documented
@Persistent
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
private static @interface ExplicitKeySpace {
@KeySpace
String name() default "";
}
}

688
src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java

@ -0,0 +1,688 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.Collection;
import org.hamcrest.core.Is;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class KeyValueTemplateUnitTests {
private static final Foo FOO_ONE = new Foo("one");
private static final Foo FOO_TWO = new Foo("two");
private static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super");
private static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub");
private static final KeyValueQuery<String> STRING_QUERY = new KeyValueQuery<String>("foo == 'two'");
private @Mock KeyValueAdapter adapterMock;
private KeyValueTemplate template;
@Before
public void setUp() throws InstantiationException, IllegalAccessException {
this.template = new KeyValueTemplate(adapterMock);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenCreatingNewTempateWithNullAdapter() {
new KeyValueTemplate(null);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenCreatingNewTempateWithNullMappingContext() {
new KeyValueTemplate(adapterMock, null);
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldLookUpValuesBeforeInserting() {
template.insert("1", FOO_ONE);
verify(adapterMock, times(1)).contains("1", Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldInsertUseClassNameAsDefaultKeyspace() {
template.insert("1", FOO_ONE);
verify(adapterMock, times(1)).put("1", FOO_ONE, Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void insertShouldThrowExceptionWhenObectWithIdAlreadyExists() {
when(adapterMock.contains(anyString(), anyString())).thenReturn(true);
template.insert("1", FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullId() {
template.insert(null, FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullObject() {
template.insert("some-id", null);
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldGenerateId() {
ClassWithStringId target = template.insert(new ClassWithStringId());
assertThat(target.id, notNullValue());
}
/**
* @see DATACMNS-525
*/
@Test(expected = DataAccessException.class)
public void insertShouldThrowErrorWhenIdCannotBeResolved() {
template.insert(FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldReturnSameInstanceGenerateId() {
ClassWithStringId source = new ClassWithStringId();
ClassWithStringId target = template.insert(source);
assertThat(target, sameInstance(source));
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldRespectExistingId() {
ClassWithStringId source = new ClassWithStringId();
source.id = "one";
template.insert(source);
verify(adapterMock, times(1)).put("one", source, ClassWithStringId.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test
public void findByIdShouldReturnNullWhenNoElementsPresent() {
assertNull(template.findById("1", Foo.class));
}
/**
* @see DATACMNS-525
*/
@Test
public void findByIdShouldReturnObjectWithMatchingIdAndType() {
template.findById("1", Foo.class);
verify(adapterMock, times(1)).get("1", Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void findByIdShouldThrowExceptionWhenGivenNullId() {
template.findById((Serializable) null, Foo.class);
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllOfShouldReturnEntireCollection() {
template.findAll(Foo.class);
verify(adapterMock, times(1)).getAllOf(Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void findAllOfShouldThrowExceptionWhenGivenNullType() {
template.findAll(null);
}
/**
* @see DATACMNS-525
*/
@Test
public void findShouldCallFindOnAdapterToResolveMatching() {
template.find(STRING_QUERY, Foo.class);
verify(adapterMock, times(1)).find(STRING_QUERY, Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test
@SuppressWarnings("rawtypes")
public void findInRangeShouldRespectOffset() {
ArgumentCaptor<KeyValueQuery> captor = ArgumentCaptor.forClass(KeyValueQuery.class);
template.findInRange(1, 5, Foo.class);
verify(adapterMock, times(1)).find(captor.capture(), eq(Foo.class.getName()));
assertThat(captor.getValue().getOffset(), is(1));
assertThat(captor.getValue().getRows(), is(5));
assertThat(captor.getValue().getCritieria(), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void updateShouldReplaceExistingObject() {
template.update("1", FOO_TWO);
verify(adapterMock, times(1)).put("1", FOO_TWO, Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void updateShouldThrowExceptionWhenGivenNullId() {
template.update(null, FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void updateShouldThrowExceptionWhenGivenNullObject() {
template.update("1", null);
}
/**
* @see DATACMNS-525
*/
@Test
public void updateShouldUseExtractedIdInformation() {
ClassWithStringId source = new ClassWithStringId();
source.id = "some-id";
template.update(source);
verify(adapterMock, times(1)).put(source.id, source, ClassWithStringId.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void updateShouldThrowErrorWhenIdInformationCannotBeExtracted() {
template.update(FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteShouldRemoveObjectCorrectly() {
template.delete("1", Foo.class);
verify(adapterMock, times(1)).delete("1", Foo.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteRemovesObjectUsingExtractedId() {
ClassWithStringId source = new ClassWithStringId();
source.id = "some-id";
template.delete(source);
verify(adapterMock, times(1)).delete("some-id", ClassWithStringId.class.getName());
}
/**
* @see DATACMNS-525
*/
@Test(expected = DataAccessException.class)
public void deleteThrowsExceptionWhenIdCannotBeExctracted() {
template.delete(FOO_ONE);
}
/**
* @see DATACMNS-525
*/
@Test
public void countShouldReturnZeroWhenNoElementsPresent() {
template.count(Foo.class);
}
/**
* @see DATACMNS-525
*/
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void countShouldReturnCollectionSize() {
Collection foo = Arrays.asList(FOO_ONE, FOO_ONE);
when(adapterMock.getAllOf(Foo.class.getName())).thenReturn(foo);
assertThat(template.count(Foo.class), is(2L));
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void countShouldThrowErrorOnNullType() {
template.count(null);
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldRespectTypeAlias() {
template.insert("1", ALIASED);
verify(adapterMock, times(1)).put("1", ALIASED, "aliased");
}
/**
* @see DATACMNS-525
*/
@Test
public void insertShouldRespectTypeAliasOnSubClass() {
template.insert("1", SUBCLASS_OF_ALIASED);
verify(adapterMock, times(1)).put("1", SUBCLASS_OF_ALIASED, "aliased");
}
/**
* @see DATACMNS-525
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void findAllOfShouldRespectTypeAliasAndFilterNonMatchingTypes() {
Collection foo = Arrays.asList(ALIASED, SUBCLASS_OF_ALIASED);
when(adapterMock.getAllOf("aliased")).thenReturn(foo);
assertThat(template.findAll(SUBCLASS_OF_ALIASED.getClass()), containsInAnyOrder(SUBCLASS_OF_ALIASED));
}
/**
* @see DATACMNS-525
*/
@Test
public void insertSouldRespectTypeAliasAndFilterNonMatching() {
template.insert("1", ALIASED);
assertThat(template.findById("1", SUBCLASS_OF_ALIASED.getClass()), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldResolveKeySpaceDefaultValueCorrectly() {
assertThat(template.getKeySpace(EntityWithDefaultKeySpace.class), Is.<Object> is("daenerys"));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldResolveKeySpaceCorrectly() {
assertThat(template.getKeySpace(EntityWithSetKeySpace.class), Is.<Object> is("viserys"));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldReturnNullWhenNoKeySpaceFoundOnComposedPersistentAnnotation() {
assertThat(template.getKeySpace(AliasedEntity.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldReturnNullWhenPersistentIsFoundOnNonComposedAnnotation() {
assertThat(template.getKeySpace(EntityWithPersistentAnnotation.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldReturnNullWhenPersistentIsNotFound() {
assertThat(template.getKeySpace(Foo.class), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldResolveInheritedKeySpaceCorrectly() {
assertThat(template.getKeySpace(EntityWithInheritedKeySpace.class), Is.<Object> is("viserys"));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldResolveDirectKeySpaceAnnotationCorrectly() {
assertThat(template.getKeySpace(ClassWithDirectKeySpaceAnnotation.class), Is.<Object> is("rhaegar"));
}
static class Foo {
String foo;
public Foo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.foo);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Foo)) {
return false;
}
Foo other = (Foo) obj;
return ObjectUtils.nullSafeEquals(this.foo, other.foo);
}
}
static class Bar {
String bar;
public Bar(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.bar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Bar)) {
return false;
}
Bar other = (Bar) obj;
return ObjectUtils.nullSafeEquals(this.bar, other.bar);
}
}
static class ClassWithStringId {
@Id String id;
String value;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.value);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithStringId)) {
return false;
}
ClassWithStringId other = (ClassWithStringId) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.value, other.value)) {
return false;
}
return true;
}
}
@ExplicitKeySpace(name = "aliased")
static class ClassWithTypeAlias {
@Id String id;
String name;
public ClassWithTypeAlias(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithTypeAlias)) {
return false;
}
ClassWithTypeAlias other = (ClassWithTypeAlias) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.name, other.name)) {
return false;
}
return true;
}
}
static class SubclassOfAliasedType extends ClassWithTypeAlias {
public SubclassOfAliasedType(String name) {
super(name);
}
}
@Persistent
static class EntityWithPersistentAnnotation {
}
@PersistentAnnotationWithExplicitKeySpace
private static class EntityWithDefaultKeySpace {
}
@PersistentAnnotationWithExplicitKeySpace(firstname = "viserys")
private static class EntityWithSetKeySpace {
}
private static class EntityWithInheritedKeySpace extends EntityWithSetKeySpace {
}
@TypeAlias("foo")
static class AliasedEntity {
}
@KeySpace("rhaegar")
static class ClassWithDirectKeySpaceAnnotation {
}
@Documented
@Persistent
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
private static @interface PersistentAnnotationWithExplicitKeySpace {
@KeySpace
String firstname() default "daenerys";
String lastnamne() default "targaryen";
}
@Documented
@Persistent
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
private static @interface ExplicitKeySpace {
@KeySpace
String name() default "";
}
}

213
src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java

@ -0,0 +1,213 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.number.OrderingComparison.*;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* @author Christoph Strobl
*/
public class SpelPropertyComperatorUnitTests {
private static final SomeType ONE = new SomeType("one", Integer.valueOf(1), 1);
private static final SomeType TWO = new SomeType("two", Integer.valueOf(2), 2);
private static final WrapperType WRAPPER_ONE = new WrapperType("w-one", ONE);
private static final WrapperType WRAPPER_TWO = new WrapperType("w-two", TWO);
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareStringAscCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("stringProperty").compare(ONE, TWO), is(ONE.getStringProperty()
.compareTo(TWO.getStringProperty())));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareStringDescCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("stringProperty").desc().compare(ONE, TWO), is(TWO
.getStringProperty().compareTo(ONE.getStringProperty())));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareIntegerAscCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("integerProperty").compare(ONE, TWO), is(ONE.getIntegerProperty()
.compareTo(TWO.getIntegerProperty())));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareIntegerDescCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("integerProperty").desc().compare(ONE, TWO), is(TWO
.getIntegerProperty().compareTo(ONE.getIntegerProperty())));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldComparePrimitiveIntegerAscCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("primitiveProperty").compare(ONE, TWO),
is(Integer.valueOf(ONE.getPrimitiveProperty()).compareTo(Integer.valueOf(TWO.getPrimitiveProperty()))));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldNotFailOnNullValues() {
new SpelPropertyComperator<SomeType>("stringProperty").compare(ONE, new SomeType(null, null, 2));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldComparePrimitiveIntegerDescCorrectly() {
assertThat(new SpelPropertyComperator<SomeType>("primitiveProperty").desc().compare(ONE, TWO),
is(Integer.valueOf(TWO.getPrimitiveProperty()).compareTo(Integer.valueOf(ONE.getPrimitiveProperty()))));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldSortNullsFirstCorrectly() {
assertThat(
new SpelPropertyComperator<SomeType>("stringProperty").nullsFirst().compare(ONE, new SomeType(null, null, 2)),
equalTo(1));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldSortNullsLastCorrectly() {
assertThat(
new SpelPropertyComperator<SomeType>("stringProperty").nullsLast().compare(ONE, new SomeType(null, null, 2)),
equalTo(-1));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareNestedTypesCorrectly() {
assertThat(new SpelPropertyComperator<WrapperType>("nestedType.stringProperty").compare(WRAPPER_ONE, WRAPPER_TWO),
is(WRAPPER_ONE.getNestedType().getStringProperty().compareTo(WRAPPER_TWO.getNestedType().getStringProperty())));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldCompareNestedTypesCorrectlyWhenOneOfThemHasNullValue() {
assertThat(new SpelPropertyComperator<WrapperType>("nestedType.stringProperty").compare(WRAPPER_ONE,
new WrapperType("two", null)), is(greaterThanOrEqualTo(1)));
}
static class WrapperType {
private String stringPropertyWrapper;
private SomeType nestedType;
public WrapperType(String stringPropertyWrapper, SomeType nestedType) {
this.stringPropertyWrapper = stringPropertyWrapper;
this.nestedType = nestedType;
}
public String getStringPropertyWrapper() {
return stringPropertyWrapper;
}
public void setStringPropertyWrapper(String stringPropertyWrapper) {
this.stringPropertyWrapper = stringPropertyWrapper;
}
public SomeType getNestedType() {
return nestedType;
}
public void setNestedType(SomeType nestedType) {
this.nestedType = nestedType;
}
}
static class SomeType {
public SomeType() {
}
public SomeType(String stringProperty, Integer integerProperty, int primitiveProperty) {
this.stringProperty = stringProperty;
this.integerProperty = integerProperty;
this.primitiveProperty = primitiveProperty;
}
String stringProperty;
Integer integerProperty;
int primitiveProperty;
public String getStringProperty() {
return stringProperty;
}
public void setStringProperty(String stringProperty) {
this.stringProperty = stringProperty;
}
public Integer getIntegerProperty() {
return integerProperty;
}
public void setIntegerProperty(Integer integerProperty) {
this.integerProperty = integerProperty;
}
public int getPrimitiveProperty() {
return primitiveProperty;
}
public void setPrimitiveProperty(int primitiveProperty) {
this.primitiveProperty = primitiveProperty;
}
}
}

405
src/test/java/org/springframework/data/keyvalue/ehcache/KeyValueTemplateTestsUsingEhCache.java vendored

@ -0,0 +1,405 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsEmptyCollection.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsSame.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.search.expression.Criteria;
import net.sf.ehcache.search.expression.EqualTo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class KeyValueTemplateTestsUsingEhCache {
private static final Foo FOO_ONE = new Foo("one");
private static final Foo FOO_TWO = new Foo("two");
private static final Foo FOO_THREE = new Foo("three");
private static final Bar BAR_ONE = new Bar("one");
private static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super");
private static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub");
private static final KeyValueQuery<Criteria> CACHE_QUERY = new KeyValueQuery<Criteria>(new EqualTo("foo", "two"));
private KeyValueTemplate operations;
@Before
public void setUp() throws InstantiationException, IllegalAccessException {
this.operations = new KeyValueTemplate(new EhCacheKeyValueAdapter(new EhCacheQueryEngine(), CacheManager.create()));
}
@After
public void tearDown() throws Exception {
this.operations.destroy();
}
@Test
public void insertShouldNotThorwErrorWhenExecutedHavingNonExistingIdAndNonNullValue() {
operations.insert("1", FOO_ONE);
}
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullId() {
operations.insert(null, FOO_ONE);
}
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullObject() {
operations.insert("some-id", null);
}
@Test(expected = InvalidDataAccessApiUsageException.class)
public void insertShouldThrowExecptionWhenObjectOfSameTypeAlreadyExists() {
operations.insert("1", FOO_ONE);
operations.insert("1", FOO_TWO);
}
@Test
public void insertShouldWorkCorrectlyWhenObjectsOfDifferentTypesWithSameIdAreInserted() {
operations.insert("1", FOO_ONE);
operations.insert("1", BAR_ONE);
}
@Test
public void createShouldReturnSameInstanceGenerateId() {
ClassWithStringId source = new ClassWithStringId();
ClassWithStringId target = operations.insert(source);
assertThat(target, sameInstance(source));
}
@Test
public void createShouldRespectExistingId() {
ClassWithStringId source = new ClassWithStringId();
source.id = "one";
operations.insert(source);
assertThat(operations.findById("one", ClassWithStringId.class), is(source));
}
@Test
public void findByIdShouldReturnObjectWithMatchingIdAndType() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
@Test
public void findByIdSouldReturnNullIfNoMatchingIdFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("2", Foo.class), nullValue());
}
@Test
public void findByIdShouldReturnNullIfNoMatchingTypeFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Bar.class), nullValue());
}
@Test
public void findShouldExecuteQueryCorrectly() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
List<Foo> result = (List<Foo>) operations.find(CACHE_QUERY, Foo.class);
assertThat(result, hasSize(1));
assertThat(result.get(0), is(FOO_TWO));
}
@Test
public void readShouldReturnEmptyCollectionIfOffsetOutOfRange() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
operations.insert("3", FOO_THREE);
assertThat(operations.findInRange(5, 5, Foo.class), empty());
}
@Test
public void updateShouldReplaceExistingObject() {
operations.insert("1", FOO_ONE);
operations.update("1", FOO_TWO);
assertThat(operations.findById("1", Foo.class), is(FOO_TWO));
}
@Test
public void updateShouldRespectTypeInformation() {
operations.insert("1", FOO_ONE);
operations.update("1", BAR_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
@Test
public void deleteShouldRemoveObjectCorrectly() {
operations.insert("1", FOO_ONE);
operations.delete("1", Foo.class);
assertThat(operations.findById("1", Foo.class), nullValue());
}
@Test
public void deleteReturnsNullWhenNotExisting() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("2", Foo.class), nullValue());
}
@Test
public void deleteReturnsRemovedObject() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("1", Foo.class), is(FOO_ONE));
}
@Test(expected = IllegalArgumentException.class)
public void deleteThrowsExceptionWhenIdCannotBeExctracted() {
operations.delete(FOO_ONE);
}
@Test
public void countShouldReturnZeroWhenNoElementsPresent() {
assertThat(operations.count(Foo.class), is(0L));
}
@Test
public void insertShouldRespectTypeAlias() {
operations.insert("1", ALIASED);
operations.insert("2", SUBCLASS_OF_ALIASED);
assertThat(operations.findAll(ALIASED.getClass()), containsInAnyOrder(ALIASED, SUBCLASS_OF_ALIASED));
}
static class Foo implements Serializable {
String foo;
public Foo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.foo);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Foo)) {
return false;
}
Foo other = (Foo) obj;
return ObjectUtils.nullSafeEquals(this.foo, other.foo);
}
}
static class Bar implements Serializable {
String bar;
public Bar(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.bar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Bar)) {
return false;
}
Bar other = (Bar) obj;
return ObjectUtils.nullSafeEquals(this.bar, other.bar);
}
}
static class ClassWithStringId implements Serializable {
@Id String id;
String value;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.value);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithStringId)) {
return false;
}
ClassWithStringId other = (ClassWithStringId) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.value, other.value)) {
return false;
}
return true;
}
}
@ExplicitKeySpace(name = "aliased")
static class ClassWithTypeAlias implements Serializable {
@Id String id;
String name;
public ClassWithTypeAlias(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithTypeAlias)) {
return false;
}
ClassWithTypeAlias other = (ClassWithTypeAlias) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.name, other.name)) {
return false;
}
return true;
}
}
static class SubclassOfAliasedType extends ClassWithTypeAlias {
public SubclassOfAliasedType(String name) {
super(name);
}
}
@Documented
@Persistent
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
private static @interface ExplicitKeySpace {
@KeySpace
String name() default "";
}
}

105
src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesUnitTests.java vendored

@ -0,0 +1,105 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.ehcache.repository.config;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.util.List;
import net.sf.ehcache.CacheManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.ehcache.EhCacheKeyValueAdapter;
import org.springframework.data.keyvalue.ehcache.EhCacheQueryEngine;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class EnableEhCacheRepositoriesUnitTests {
@Configuration
@EnableEhCacheRepositories(considerNestedRepositories = true)
static class Config {
@Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new EhCacheKeyValueAdapter(new EhCacheQueryEngine(), CacheManager.create()));
}
}
@Autowired PersonRepository repo;
@Test
public void shouldEnableKeyValueRepositoryCorrectly() {
assertThat(repo, notNullValue());
Person person = new Person();
person.setFirstname("foo");
repo.save(person);
List<Person> result = repo.findByFirstname("foo");
assertThat(result, hasSize(1));
assertThat(result.get(0).firstname, is("foo"));
}
static class Person implements Serializable {
private static final long serialVersionUID = -1654603912377346292L;
@Id String id;
String firstname;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
static interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByFirstname(String firstname);
}
}

42
src/test/java/org/springframework/data/keyvalue/hazelcast/HazelcastUtils.java

@ -0,0 +1,42 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast;
import org.springframework.data.keyvalue.hazelcast.HazelcastKeyValueAdapter;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
/**
* @author Christoph Strobl
*/
public class HazelcastUtils {
static Config hazelcastConfig() {
Config hazelcastConfig = new Config();
hazelcastConfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
hazelcastConfig.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(false);
hazelcastConfig.getNetworkConfig().getJoin().getAwsConfig().setEnabled(false);
return hazelcastConfig;
}
public static HazelcastKeyValueAdapter preconfiguredHazelcastKeyValueAdapter() {
return new HazelcastKeyValueAdapter(Hazelcast.newHazelcastInstance(hazelcastConfig()));
}
}

405
src/test/java/org/springframework/data/keyvalue/hazelcast/KeyValueTemplateTestsUsingHazelcast.java

@ -0,0 +1,405 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsEmptyCollection.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsSame.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.util.ObjectUtils;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
/**
* @author Christoph Strobl
*/
public class KeyValueTemplateTestsUsingHazelcast {
private static final Foo FOO_ONE = new Foo("one");
private static final Foo FOO_TWO = new Foo("two");
private static final Foo FOO_THREE = new Foo("three");
private static final Bar BAR_ONE = new Bar("one");
private static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super");
private static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub");
private static final KeyValueQuery<Predicate<?, ?>> HAZELCAST_QUERY = new KeyValueQuery<Predicate<?, ?>>(
new PredicateBuilder().getEntryObject().get("foo").equal("two"));
private KeyValueTemplate operations;
@Before
public void setUp() throws InstantiationException, IllegalAccessException {
this.operations = new KeyValueTemplate(HazelcastUtils.preconfiguredHazelcastKeyValueAdapter());
}
@After
public void tearDown() throws Exception {
this.operations.destroy();
}
@Test
public void insertShouldNotThorwErrorWhenExecutedHavingNonExistingIdAndNonNullValue() {
operations.insert("1", FOO_ONE);
}
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullId() {
operations.insert(null, FOO_ONE);
}
@Test(expected = IllegalArgumentException.class)
public void insertShouldThrowExceptionForNullObject() {
operations.insert("some-id", null);
}
@Test(expected = InvalidDataAccessApiUsageException.class)
public void insertShouldThrowExecptionWhenObjectOfSameTypeAlreadyExists() {
operations.insert("1", FOO_ONE);
operations.insert("1", FOO_TWO);
}
@Test
public void insertShouldWorkCorrectlyWhenObjectsOfDifferentTypesWithSameIdAreInserted() {
operations.insert("1", FOO_ONE);
operations.insert("1", BAR_ONE);
}
@Test
public void createShouldReturnSameInstanceGenerateId() {
ClassWithStringId source = new ClassWithStringId();
ClassWithStringId target = operations.insert(source);
assertThat(target, sameInstance(source));
}
@Test
public void createShouldRespectExistingId() {
ClassWithStringId source = new ClassWithStringId();
source.id = "one";
operations.insert(source);
assertThat(operations.findById("one", ClassWithStringId.class), is(source));
}
@Test
public void findByIdShouldReturnObjectWithMatchingIdAndType() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
@Test
public void findByIdSouldReturnNullIfNoMatchingIdFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("2", Foo.class), nullValue());
}
@Test
public void findByIdShouldReturnNullIfNoMatchingTypeFound() {
operations.insert("1", FOO_ONE);
assertThat(operations.findById("1", Bar.class), nullValue());
}
@Test
public void findShouldExecuteQueryCorrectly() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
List<Foo> result = (List<Foo>) operations.find(HAZELCAST_QUERY, Foo.class);
assertThat(result, hasSize(1));
assertThat(result.get(0), is(FOO_TWO));
}
@Test
public void readShouldReturnEmptyCollectionIfOffsetOutOfRange() {
operations.insert("1", FOO_ONE);
operations.insert("2", FOO_TWO);
operations.insert("3", FOO_THREE);
assertThat(operations.findInRange(5, 5, Foo.class), empty());
}
@Test
public void updateShouldReplaceExistingObject() {
operations.insert("1", FOO_ONE);
operations.update("1", FOO_TWO);
assertThat(operations.findById("1", Foo.class), is(FOO_TWO));
}
@Test
public void updateShouldRespectTypeInformation() {
operations.insert("1", FOO_ONE);
operations.update("1", BAR_ONE);
assertThat(operations.findById("1", Foo.class), is(FOO_ONE));
}
@Test
public void deleteShouldRemoveObjectCorrectly() {
operations.insert("1", FOO_ONE);
operations.delete("1", Foo.class);
assertThat(operations.findById("1", Foo.class), nullValue());
}
@Test
public void deleteReturnsNullWhenNotExisting() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("2", Foo.class), nullValue());
}
@Test
public void deleteReturnsRemovedObject() {
operations.insert("1", FOO_ONE);
assertThat(operations.delete("1", Foo.class), is(FOO_ONE));
}
@Test(expected = IllegalArgumentException.class)
public void deleteThrowsExceptionWhenIdCannotBeExctracted() {
operations.delete(FOO_ONE);
}
@Test
public void countShouldReturnZeroWhenNoElementsPresent() {
assertThat(operations.count(Foo.class), is(0L));
}
@Test
public void insertShouldRespectTypeAlias() {
operations.insert("1", ALIASED);
operations.insert("2", SUBCLASS_OF_ALIASED);
assertThat(operations.findAll(ALIASED.getClass()), containsInAnyOrder(ALIASED, SUBCLASS_OF_ALIASED));
}
static class Foo implements Serializable {
String foo;
public Foo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.foo);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Foo)) {
return false;
}
Foo other = (Foo) obj;
return ObjectUtils.nullSafeEquals(this.foo, other.foo);
}
}
static class Bar implements Serializable {
String bar;
public Bar(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.bar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Bar)) {
return false;
}
Bar other = (Bar) obj;
return ObjectUtils.nullSafeEquals(this.bar, other.bar);
}
}
static class ClassWithStringId implements Serializable {
@Id String id;
String value;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.value);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithStringId)) {
return false;
}
ClassWithStringId other = (ClassWithStringId) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.value, other.value)) {
return false;
}
return true;
}
}
@ExplicitKeySpace(name = "aliased")
static class ClassWithTypeAlias implements Serializable {
@Id String id;
String name;
public ClassWithTypeAlias(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.id);
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ClassWithTypeAlias)) {
return false;
}
ClassWithTypeAlias other = (ClassWithTypeAlias) obj;
if (!ObjectUtils.nullSafeEquals(this.id, other.id)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.name, other.name)) {
return false;
}
return true;
}
}
static class SubclassOfAliasedType extends ClassWithTypeAlias {
public SubclassOfAliasedType(String name) {
super(name);
}
}
@Documented
@Persistent
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
private static @interface ExplicitKeySpace {
@KeySpace
String name() default "";
}
}

102
src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesUnitTests.java

@ -0,0 +1,102 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.hazelcast.repository.config;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.hazelcast.HazelcastUtils;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class EnableHazelcastRepositoriesUnitTests {
@Configuration
@EnableHazelcastRepositories(considerNestedRepositories = true)
static class Config {
@Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(HazelcastUtils.preconfiguredHazelcastKeyValueAdapter());
}
}
@Autowired PersonRepository repo;
@Test
public void shouldEnableKeyValueRepositoryCorrectly() {
assertThat(repo, notNullValue());
Person person = new Person();
person.setFirstname("foo");
repo.save(person);
List<Person> result = repo.findByFirstname("foo");
assertThat(result, hasSize(1));
assertThat(result.get(0).firstname, is("foo"));
}
static class Person implements Serializable {
private static final long serialVersionUID = -1654603912377346292L;
@Id String id;
String firstname;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
static interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByFirstname(String firstname);
}
}

170
src/test/java/org/springframework/data/keyvalue/map/MapBackedKeyValueRepositoryUnitTests.java

@ -0,0 +1,170 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.collection.IsCollectionWithSize;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.keyvalue.Person;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
import org.springframework.data.repository.CrudRepository;
/**
* @author Christoph Strobl
*/
public class MapBackedKeyValueRepositoryUnitTests {
protected static final Person CERSEI = new Person("cersei", 19);
protected static final Person JAIME = new Person("jaime", 19);
protected static final Person TYRION = new Person("tyrion", 17);
protected static List<Person> LENNISTERS = Arrays.asList(CERSEI, JAIME, TYRION);
protected PersonRepository repository;
protected KeyValueTemplate template = new KeyValueTemplate(new MapKeyValueAdapter());
@Before
public void setup() {
this.repository = new KeyValueRepositoryFactory(template).getRepository(getRepositoryClass());
}
/**
* @see DATACMNS-525
*/
@Test
public void findBy() {
this.repository.save(LENNISTERS);
assertThat(this.repository.findByAge(19), containsInAnyOrder(CERSEI, JAIME));
}
/**
* @see DATACMNS-525
*/
@Test
public void combindedFindUsingAnd() {
this.repository.save(LENNISTERS);
assertThat(this.repository.findByFirstnameAndAge(JAIME.getFirstname(), 19), containsInAnyOrder(JAIME));
}
/**
* @see DATACMNS-525
*/
@Test
public void findPage() {
this.repository.save(LENNISTERS);
Page<Person> page = this.repository.findByAge(19, new PageRequest(0, 1));
assertThat(page.hasNext(), is(true));
assertThat(page.getTotalElements(), is(2L));
assertThat(page.getContent(), IsCollectionWithSize.hasSize(1));
Page<Person> next = this.repository.findByAge(19, page.nextPageable());
assertThat(next.hasNext(), is(false));
assertThat(next.getTotalElements(), is(2L));
assertThat(next.getContent(), IsCollectionWithSize.hasSize(1));
}
/**
* @see DATACMNS-525
*/
@Test
public void findByConnectingOr() {
this.repository.save(LENNISTERS);
assertThat(this.repository.findByAgeOrFirstname(19, TYRION.getFirstname()),
containsInAnyOrder(CERSEI, JAIME, TYRION));
}
/**
* @see DATACMNS-525
*/
@Test
public void singleEntityExecution() {
this.repository.save(LENNISTERS);
assertThat(this.repository.findByAgeAndFirstname(TYRION.getAge(), TYRION.getFirstname()), is(TYRION));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllShouldRespectSort() {
this.repository.save(LENNISTERS);
assertThat(this.repository.findAll(new Sort(new Sort.Order(Direction.ASC, "age"), new Sort.Order(Direction.DESC,
"firstname"))), IsIterableContainingInOrder.contains(TYRION, JAIME, CERSEI));
}
/**
* @see DATACMNS-525
*/
@Test
public void derivedFinderShouldRespectSort() {
repository.save(LENNISTERS);
List<Person> result = repository.findByAgeGreaterThanOrderByAgeAscFirstnameDesc(2);
assertThat(result, contains(TYRION, JAIME, CERSEI));
}
protected Class<? extends PersonRepository> getRepositoryClass() {
return PersonRepository.class;
}
public static interface PersonRepository extends CrudRepository<Person, String>, KeyValueRepository<Person, String> {
List<Person> findByAge(int age);
List<Person> findByFirstname(String firstname);
List<Person> findByFirstnameAndAge(String firstname, int age);
Page<Person> findByAge(int age, Pageable page);
List<Person> findByAgeOrFirstname(int age, String firstname);
Person findByAgeAndFirstname(int age, String firstname);
List<Person> findByAgeGreaterThanOrderByAgeAscFirstnameDesc(int age);
}
}

96
src/test/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactoryUnitTests.java

@ -0,0 +1,96 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsInstanceOf.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.junit.Test;
import org.springframework.core.CollectionFactory;
/**
* @author Christoph Strobl
*/
public class MapKeyValueAdapterFactoryUnitTests {
/**
* @see DATACMNS-525
*/
@Test
public void shouldDefaultToConcurrentHashMapWhenTypeIsNull() {
assertThat(new MapKeyValueAdapterFactory(null).getAdapter().getKeySpaceMap("foo"),
instanceOf(ConcurrentHashMap.class));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldDefaultToCollecitonUtilsDefaultForInterfaceTypes() {
assertThat(new MapKeyValueAdapterFactory(Map.class).getAdapter().getKeySpaceMap("foo"),
instanceOf(CollectionFactory.createMap(Map.class, 0).getClass()));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldUseConcreteMapTypeWhenInstantiable() {
assertThat(new MapKeyValueAdapterFactory(ConcurrentSkipListMap.class).getAdapter().getKeySpaceMap("foo"),
instanceOf(CollectionFactory.createMap(ConcurrentSkipListMap.class, 0).getClass()));
}
/**
* @see DATACMNS-525
*/
@Test
public void shouldPopulateAdapterWithValues() {
MapKeyValueAdapterFactory factory = new MapKeyValueAdapterFactory();
factory.setInitialValuesForKeyspace("foo", Collections.singletonMap("1", "STANIS"));
factory.setInitialValuesForKeyspace("bar", Collections.singletonMap("1", "ROBERT"));
assertThat((String) factory.getAdapter().get("1", "foo"), equalTo("STANIS"));
assertThat((String) factory.getAdapter().get("1", "bar"), equalTo("ROBERT"));
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenSettingValuesForNullKeySpace() {
new MapKeyValueAdapterFactory().setInitialValuesForKeyspace(null, Collections.<Serializable, Object> emptyMap());
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenSettingNullValuesForKeySpace() {
new MapKeyValueAdapterFactory().setInitialValuesForKeyspace("foo", null);
}
}

222
src/test/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterUnitTests.java

@ -0,0 +1,222 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class MapKeyValueAdapterUnitTests {
private static final String COLLECTION_1 = "collection-1";
private static final String COLLECTION_2 = "collection-2";
private static final String STRING_1 = new String("1");
private Object object1 = new SimpleObject("one");
private Object object2 = new SimpleObject("two");
private MapKeyValueAdapter adapter;
@Before
public void setUp() {
this.adapter = new MapKeyValueAdapter();
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void putShouldThrowExceptionWhenAddingNullId() {
adapter.put(null, object1, COLLECTION_1);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void putShouldThrowExceptionWhenCollectionIsNullValue() {
adapter.put("1", object1, null);
}
/**
* @see DATACMNS-525
*/
@Test
public void putReturnsNullWhenNoObjectForIdPresent() {
assertThat(adapter.put("1", object1, COLLECTION_1), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void putShouldReturnPreviousObjectForIdWhenAddingNewOneWithSameIdPresent() {
adapter.put("1", object1, COLLECTION_1);
assertThat(adapter.put("1", object2, COLLECTION_1), equalTo(object1));
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void containsShouldThrowExceptionWhenIdIsNull() {
adapter.contains(null, COLLECTION_1);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void containsShouldThrowExceptionWhenTypeIsNull() {
adapter.contains("", null);
}
/**
* @see DATACMNS-525
*/
@Test
public void containsShouldReturnFalseWhenNoElementsPresent() {
assertThat(adapter.contains("1", COLLECTION_1), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void containShouldReturnTrueWhenElementWithIdPresent() {
adapter.put("1", object1, COLLECTION_1);
assertThat(adapter.contains("1", COLLECTION_1), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void getShouldReturnNullWhenNoElementWithIdPresent() {
assertThat(adapter.get("1", COLLECTION_1), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void getShouldReturnElementWhenMatchingIdPresent() {
adapter.put("1", object1, COLLECTION_1);
assertThat(adapter.get("1", COLLECTION_1), is(object1));
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void getShouldThrowExceptionWhenIdIsNull() {
adapter.get(null, COLLECTION_1);
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void getShouldThrowExceptionWhenTypeIsNull() {
adapter.get("1", null);
}
/**
* @see DATACMNS-525
*/
@Test
public void getAllOfShouldReturnAllValuesOfGivenCollection() {
adapter.put("1", object1, COLLECTION_1);
adapter.put("2", object2, COLLECTION_1);
adapter.put("3", STRING_1, COLLECTION_2);
assertThat(adapter.getAllOf(COLLECTION_1), containsInAnyOrder(object1, object2));
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void getAllOfShouldThrowExceptionWhenTypeIsNull() {
adapter.getAllOf(null);
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteShouldReturnNullWhenGivenIdThatDoesNotExist() {
assertThat(adapter.delete("1", COLLECTION_1), nullValue());
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteShouldReturnDeletedObject() {
adapter.put("1", object1, COLLECTION_1);
assertThat(adapter.delete("1", COLLECTION_1), is(object1));
}
static class SimpleObject {
protected String stringValue;
public SimpleObject() {}
SimpleObject(String value) {
this.stringValue = value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * ObjectUtils.nullSafeHashCode(this.stringValue);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof SimpleObject)) {
return false;
}
SimpleObject that = (SimpleObject) obj;
return ObjectUtils.nullSafeEquals(this.stringValue, that.stringValue);
}
}
}

138
src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java

@ -0,0 +1,138 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.map;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.keyvalue.Person;
import org.springframework.data.keyvalue.QPerson;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
/**
* @author Christoph Strobl
*/
public class QueryDslMapRepositoryUnitTests extends MapBackedKeyValueRepositoryUnitTests {
/**
* @see DATACMNS-525
*/
@Test
public void findOneIsExecutedCorrectly() {
repository.save(LENNISTERS);
Person result = getQPersonRepo().findOne(QPerson.person.firstname.eq(CERSEI.getFirstname()));
assertThat(result, is(CERSEI));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllIsExecutedCorrectly() {
repository.save(LENNISTERS);
Iterable<Person> result = getQPersonRepo().findAll(QPerson.person.age.eq(CERSEI.getAge()));
assertThat(result, containsInAnyOrder(CERSEI, JAIME));
}
/**
* @see DATACMNS-525
*/
@Test
public void findWithPaginationWorksCorrectly() {
repository.save(LENNISTERS);
Page<Person> page1 = getQPersonRepo().findAll(QPerson.person.age.eq(CERSEI.getAge()), new PageRequest(0, 1));
assertThat(page1.getTotalElements(), is(2L));
assertThat(page1.getContent(), hasSize(1));
assertThat(page1.hasNext(), is(true));
Page<Person> page2 = ((QPersonRepository) repository).findAll(QPerson.person.age.eq(CERSEI.getAge()),
page1.nextPageable());
assertThat(page2.getTotalElements(), is(2L));
assertThat(page2.getContent(), hasSize(1));
assertThat(page2.hasNext(), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllUsingOrderSpecifierWorksCorrectly() {
repository.save(LENNISTERS);
Iterable<Person> result = getQPersonRepo().findAll(QPerson.person.age.eq(CERSEI.getAge()),
QPerson.person.firstname.desc());
assertThat(result, contains(JAIME, CERSEI));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllUsingPageableWithSortWorksCorrectly() {
repository.save(LENNISTERS);
Iterable<Person> result = getQPersonRepo().findAll(QPerson.person.age.eq(CERSEI.getAge()),
new PageRequest(0, 10, Direction.DESC, "firstname"));
assertThat(result, contains(JAIME, CERSEI));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllUsingPagableWithQSortWorksCorrectly() {
repository.save(LENNISTERS);
Iterable<Person> result = getQPersonRepo().findAll(QPerson.person.age.eq(CERSEI.getAge()),
new PageRequest(0, 10, new QSort(QPerson.person.firstname.desc())));
assertThat(result, contains(JAIME, CERSEI));
}
@Override
protected Class<? extends PersonRepository> getRepositoryClass() {
return QPersonRepository.class;
}
QPersonRepository getQPersonRepo() {
return ((QPersonRepository) repository);
}
static interface QPersonRepository extends PersonRepository, QueryDslPredicateExecutor<Person> {
}
}

206
src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java

@ -0,0 +1,206 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.repository.core.support.ReflectionEntityInformation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class BasicKeyValueRepositoryUnitTests {
private BasicKeyValueRepository<Foo, String> repo;
private @Mock KeyValueOperations opsMock;
@Before
public void setUp() {
ReflectionEntityInformation<Foo, String> ei = new ReflectionEntityInformation<Foo, String>(Foo.class);
repo = new BasicKeyValueRepository<Foo, String>(ei, opsMock);
}
/**
* @see DATACMNS-525
*/
@Test
public void saveNewWithNumericId() {
ReflectionEntityInformation<WithNumericId, Integer> ei = new ReflectionEntityInformation<WithNumericId, Integer>(
WithNumericId.class);
BasicKeyValueRepository<WithNumericId, Integer> temp = new BasicKeyValueRepository<WithNumericId, Integer>(ei,
opsMock);
WithNumericId foo = temp.save(new WithNumericId());
}
/**
* @see DATACMNS-525
*/
@Test
public void testDoubleSave() {
Foo foo = new Foo("one");
repo.save(foo);
foo.id = "1";
repo.save(foo);
verify(opsMock, times(1)).insert(eq(foo));
verify(opsMock, times(1)).update(eq(foo.getId()), eq(foo));
}
/**
* @see DATACMNS-525
*/
@Test
public void multipleSave() {
Foo one = new Foo("one");
Foo two = new Foo("one");
repo.save(Arrays.asList(one, two));
verify(opsMock, times(1)).insert(eq(one));
verify(opsMock, times(1)).insert(eq(two));
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteEntity() {
Foo one = repo.save(new Foo("one"));
repo.delete(one);
verify(opsMock, times(1)).delete(eq(one.getId()), eq(Foo.class));
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteById() {
repo.delete("one");
verify(opsMock, times(1)).delete(eq("one"), eq(Foo.class));
}
/**
* @see DATACMNS-525
*/
@Test
public void deleteAll() {
repo.deleteAll();
verify(opsMock, times(1)).delete(eq(Foo.class));
}
/**
* @see DATACMNS-525
*/
@Test
public void findAllIds() {
repo.findAll(Arrays.asList("one", "two", "three"));
verify(opsMock, times(3)).findById(anyString(), eq(Foo.class));
}
static class Foo {
private @Id String id;
private Long longValue;
private String name;
private Bar bar;
public Foo() {
}
public Foo(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getLongValue() {
return longValue;
}
public void setLongValue(Long longValue) {
this.longValue = longValue;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
static class Bar {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
@Persistent
static class WithNumericId {
@Id Integer id;
}
}

92
src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarUnitTests.java

@ -0,0 +1,92 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.config;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.map.MapKeyValueAdapter;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class KeyValueRepositoryRegistrarUnitTests {
@Configuration
@EnableKeyValueRepositories(considerNestedRepositories = true)
static class Config {
@Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new MapKeyValueAdapter());
}
}
@Autowired PersonRepository repo;
/**
* @see DATACMNS-525
*/
@Test
public void shouldEnableKeyValueRepositoryCorrectly() {
assertThat(repo, notNullValue());
}
static class Person {
@Id String id;
String firstname;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
static interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByFirstname(String firstname);
}
}

567
src/test/java/org/springframework/data/keyvalue/repository/query/SpELQueryCreatorUnitTests.java

@ -0,0 +1,567 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.repository.query;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Date;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class SpELQueryCreatorUnitTests {
private static final DateTimeFormatter FORMATTER = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC();
private static final Person RICKON = new Person("rickon", 4);
private static final Person BRAN = new Person("bran", 9)//
.skinChanger(true)//
.bornAt(FORMATTER.parseDateTime("2013-01-31T06:00:00Z").toDate());
private static final Person ARYA = new Person("arya", 13);
private static final Person ROBB = new Person("robb", 16)//
.named("stark")//
.bornAt(FORMATTER.parseDateTime("2010-09-20T06:00:00Z").toDate());
private static final Person JON = new Person("jon", 17).named("snow");
private @Mock RepositoryMetadata metadataMock;
/**
* @see DATACMNS-525
*/
@Test
public void equalsReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByFirstname", BRAN.firstname).against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void equalsReturnsFalseWhenNotMatching() throws Exception {
assertThat(evaluate("findByFirstname", BRAN.firstname).against(RICKON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void isTrueAssertedPropertlyWhenTrue() throws Exception {
assertThat(evaluate("findBySkinChangerIsTrue").against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void isTrueAssertedPropertlyWhenFalse() throws Exception {
assertThat(evaluate("findBySkinChangerIsTrue").against(RICKON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void isFalseAssertedPropertlyWhenTrue() throws Exception {
assertThat(evaluate("findBySkinChangerIsFalse").against(BRAN), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void isFalseAssertedPropertlyWhenFalse() throws Exception {
assertThat(evaluate("findBySkinChangerIsFalse").against(RICKON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void isNullAssertedPropertlyWhenAttributeIsNull() throws Exception {
assertThat(evaluate("findByLastnameIsNull").against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void isNullAssertedPropertlyWhenAttributeIsNotNull() throws Exception {
assertThat(evaluate("findByLastnameIsNull").against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void isNotNullFalseTrueWhenAttributeIsNull() throws Exception {
assertThat(evaluate("findByLastnameIsNotNull").against(BRAN), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void isNotNullReturnsTrueAttributeIsNotNull() throws Exception {
assertThat(evaluate("findByLastnameIsNotNull").against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void startsWithReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByFirstnameStartingWith", "r").against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void startsWithReturnsFalseWhenNotMatching() throws Exception {
assertThat(evaluate("findByFirstnameStartingWith", "r").against(BRAN), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void likeReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByFirstnameLike", "ob").against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void likeReturnsFalseWhenNotMatching() throws Exception {
assertThat(evaluate("findByFirstnameLike", "ra").against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void endsWithReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByFirstnameEndingWith", "bb").against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void endsWithReturnsFalseWhenNotMatching() throws Exception {
assertThat(evaluate("findByFirstnameEndingWith", "an").against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void startsWithIgnoreCaseReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByFirstnameIgnoreCase", "R").against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void greaterThanReturnsTrueForHigherValues() throws Exception {
assertThat(evaluate("findByAgeGreaterThan", BRAN.age).against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void greaterThanReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByAgeGreaterThan", BRAN.age).against(RICKON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void afterReturnsTrueForHigherValues() throws Exception {
assertThat(evaluate("findByBirthdayAfter", ROBB.birthday).against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void afterReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByBirthdayAfter", BRAN.birthday).against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void greaterThanEaualsReturnsTrueForHigherValues() throws Exception {
assertThat(evaluate("findByAgeGreaterThanEqual", BRAN.age).against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void greaterThanEaualsReturnsTrueForEqualValues() throws Exception {
assertThat(evaluate("findByAgeGreaterThanEqual", BRAN.age).against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void greaterThanEqualsReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByAgeGreaterThanEqual", BRAN.age).against(RICKON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void lessThanReturnsTrueForHigherValues() throws Exception {
assertThat(evaluate("findByAgeLessThan", BRAN.age).against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void lessThanReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByAgeLessThan", BRAN.age).against(RICKON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void beforeReturnsTrueForLowerValues() throws Exception {
assertThat(evaluate("findByBirthdayBefore", BRAN.birthday).against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void beforeReturnsFalseForHigherValues() throws Exception {
assertThat(evaluate("findByBirthdayBefore", ROBB.birthday).against(BRAN), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void lessThanEaualsReturnsTrueForHigherValues() throws Exception {
assertThat(evaluate("findByAgeLessThanEqual", BRAN.age).against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void lessThanEaualsReturnsTrueForEqualValues() throws Exception {
assertThat(evaluate("findByAgeLessThanEqual", BRAN.age).against(BRAN), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void lessThanEqualsReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByAgeLessThanEqual", BRAN.age).against(RICKON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void betweenEqualsReturnsTrueForValuesInBetween() throws Exception {
assertThat(evaluate("findByAgeBetween", BRAN.age, ROBB.age).against(ARYA), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void betweenEqualsReturnsFalseForHigherValues() throws Exception {
assertThat(evaluate("findByAgeBetween", BRAN.age, ROBB.age).against(JON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void betweenEqualsReturnsFalseForLowerValues() throws Exception {
assertThat(evaluate("findByAgeBetween", BRAN.age, ROBB.age).against(RICKON), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void connectByAndReturnsTrueWhenAllPropertiesMatching() throws Exception {
assertThat(evaluate("findByAgeGreaterThanAndLastname", BRAN.age, JON.lastname).against(JON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void connectByAndReturnsFalseWhenOnlyFewPropertiesMatch() throws Exception {
assertThat(evaluate("findByAgeGreaterThanAndLastname", BRAN.age, JON.lastname).against(ROBB), is(false));
}
/**
* @see DATACMNS-525
*/
@Test
public void connectByOrReturnsTrueWhenOnlyFewPropertiesMatch() throws Exception {
assertThat(evaluate("findByAgeGreaterThanOrLastname", BRAN.age, JON.lastname).against(ROBB), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void connectByOrReturnsTrueWhenAllPropertiesMatch() throws Exception {
assertThat(evaluate("findByAgeGreaterThanOrLastname", BRAN.age, JON.lastname).against(JON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void regexReturnsTrueWhenMatching() throws Exception {
assertThat(evaluate("findByLastnameMatches", "^s.*w$").against(JON), is(true));
}
/**
* @see DATACMNS-525
*/
@Test
public void regexReturnsFalseWhenNotMatching() throws Exception {
assertThat(evaluate("findByLastnameMatches", "^s.*w$").against(ROBB), is(false));
}
Evaluation evaluate(String methodName, Object... args) throws Exception {
return new Evaluation((SpelExpression) createQueryForMethodWithArgs(methodName, args).getCritieria());
}
private KeyValueQuery<SpelExpression> createQueryForMethodWithArgs(String methodName, Object... args)
throws NoSuchMethodException, SecurityException {
Class<?>[] argTypes = new Class<?>[args.length];
if (!ObjectUtils.isEmpty(args)) {
for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass();
}
}
Method method = PersonRepository.class.getMethod(methodName, argTypes);
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
SpelQueryCreator creator = new SpelQueryCreator(partTree, new ParametersParameterAccessor(new QueryMethod(method,
metadataMock).getParameters(), args));
KeyValueQuery<SpelExpression> q = creator.createQuery();
q.getCritieria().setEvaluationContext(new StandardEvaluationContext(args));
return q;
}
static interface PersonRepository {
// Type.SIMPLE_PROPERTY
Person findByFirstname(String firstname);
// Type.TRUE
Person findBySkinChangerIsTrue();
// Type.FALSE
Person findBySkinChangerIsFalse();
// Type.IS_NULL
Person findByLastnameIsNull();
// Type.IS_NOT_NULL
Person findByLastnameIsNotNull();
// Type.STARTING_WITH
Person findByFirstnameStartingWith(String firstanme);
Person findByFirstnameIgnoreCase(String firstanme);
// Type.AFTER
Person findByBirthdayAfter(Date date);
// Type.GREATHER_THAN
Person findByAgeGreaterThan(Integer age);
// Type.GREATER_THAN_EQUAL
Person findByAgeGreaterThanEqual(Integer age);
// Type.BEFORE
Person findByBirthdayBefore(Date date);
// Type.LESS_THAN
Person findByAgeLessThan(Integer age);
// Type.LESS_THAN_EQUAL
Person findByAgeLessThanEqual(Integer age);
// Type.BETWEEN
Person findByAgeBetween(Integer low, Integer high);
// Type.LIKE
Person findByFirstnameLike(String firstname);
// Type.ENDING_WITH
Person findByFirstnameEndingWith(String firstname);
Person findByAgeGreaterThanAndLastname(Integer age, String lastname);
Person findByAgeGreaterThanOrLastname(Integer age, String lastname);
// Type.REGEX
Person findByLastnameMatches(String lastname);
}
static class Evaluation {
SpelExpression expression;
Object candidate;
public Evaluation(SpelExpression expresison) {
this.expression = expresison;
}
public Boolean against(Object candidate) {
this.candidate = candidate;
return evaluate();
}
private boolean evaluate() {
expression.getEvaluationContext().setVariable("it", candidate);
return expression.getValue(Boolean.class);
}
}
static class Person {
private @Id String id;
private String firstname, lastname;
private int age;
private boolean isSkinChanger = false;
private Date birthday;
public Person() {}
public Person(String firstname, int age) {
super();
this.firstname = firstname;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public boolean isSkinChanger() {
return isSkinChanger;
}
public void setSkinChanger(boolean isSkinChanger) {
this.isSkinChanger = isSkinChanger;
}
public Person skinChanger(boolean isSkinChanger) {
this.isSkinChanger = isSkinChanger;
return this;
}
public Person named(String lastname) {
this.lastname = lastname;
return this;
}
public Person bornAt(Date date) {
this.birthday = date;
return this;
}
}
}

24
src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java

@ -48,6 +48,7 @@ import org.springframework.test.util.ReflectionTestUtils;
* Unit test for {@link BasicPersistentEntity}. * Unit test for {@link BasicPersistentEntity}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> { public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@ -58,7 +59,7 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@Test @Test
public void assertInvariants() { public void assertInvariants() {
PersistentEntitySpec.assertInvariants(createEntity(null)); PersistentEntitySpec.assertInvariants(createEntity(Person.class));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
@ -68,21 +69,20 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void rejectsNullProperty() { public void rejectsNullProperty() {
createEntity(null).addPersistentProperty(null); createEntity(Person.class, null).addPersistentProperty(null);
} }
@Test @Test
public void returnsNullForTypeAliasIfNoneConfigured() { public void returnsNullForTypeAliasIfNoneConfigured() {
PersistentEntity<Entity, T> entity = new BasicPersistentEntity<Entity, T>(ClassTypeInformation.from(Entity.class)); PersistentEntity<Entity, T> entity = createEntity(Entity.class);
assertThat(entity.getTypeAlias(), is(nullValue())); assertThat(entity.getTypeAlias(), is(nullValue()));
} }
@Test @Test
public void returnsTypeAliasIfAnnotated() { public void returnsTypeAliasIfAnnotated() {
PersistentEntity<AliasedEntity, T> entity = new BasicPersistentEntity<AliasedEntity, T>( PersistentEntity<AliasedEntity, T> entity = createEntity(AliasedEntity.class);
ClassTypeInformation.from(AliasedEntity.class));
assertThat(entity.getTypeAlias(), is((Object) "foo")); assertThat(entity.getTypeAlias(), is((Object) "foo"));
} }
@ -93,7 +93,7 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void considersComparatorForPropertyOrder() { public void considersComparatorForPropertyOrder() {
BasicPersistentEntity<Person, T> entity = createEntity(new Comparator<T>() { BasicPersistentEntity<Person, T> entity = createEntity(Person.class, new Comparator<T>() {
public int compare(T o1, T o2) { public int compare(T o1, T o2) {
return o1.getName().compareTo(o2.getName()); return o1.getName().compareTo(o2.getName());
} }
@ -127,7 +127,7 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@Test @Test
public void addingAndIdPropertySetsIdPropertyInternally() { public void addingAndIdPropertySetsIdPropertyInternally() {
MutablePersistentEntity<Person, T> entity = createEntity(null); MutablePersistentEntity<Person, T> entity = createEntity(Person.class);
assertThat(entity.getIdProperty(), is(nullValue())); assertThat(entity.getIdProperty(), is(nullValue()));
when(property.isIdProperty()).thenReturn(true); when(property.isIdProperty()).thenReturn(true);
@ -141,7 +141,7 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@Test @Test
public void rejectsIdPropertyIfAlreadySet() { public void rejectsIdPropertyIfAlreadySet() {
MutablePersistentEntity<Person, T> entity = createEntity(null); MutablePersistentEntity<Person, T> entity = createEntity(Person.class);
when(property.isIdProperty()).thenReturn(true); when(property.isIdProperty()).thenReturn(true);
@ -222,8 +222,12 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
assertThat(entity.getPropertyAccessor(new Subtype()), is(notNullValue())); assertThat(entity.getPropertyAccessor(new Subtype()), is(notNullValue()));
} }
private BasicPersistentEntity<Person, T> createEntity(Comparator<T> comparator) { private <S> BasicPersistentEntity<S, T> createEntity(Class<S> type) {
return new BasicPersistentEntity<Person, T>(ClassTypeInformation.from(Person.class), comparator); return createEntity(type, null);
}
private <S> BasicPersistentEntity<S, T> createEntity(Class<S> type, Comparator<T> comparator) {
return new BasicPersistentEntity<S, T>(ClassTypeInformation.from(type), comparator);
} }
@TypeAlias("foo") @TypeAlias("foo")

120
src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java

@ -0,0 +1,120 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.querydsl;
import static org.hamcrest.collection.IsArrayWithSize.*;
import static org.junit.Assert.*;
import org.hamcrest.collection.IsArrayContainingInOrder;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.NullHandling;
import org.springframework.data.keyvalue.Person;
import org.springframework.data.keyvalue.QPerson;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.path.PathBuilder;
/**
* @author Christoph Strobl
*/
public class QueryDslUtilsUnitTests {
private EntityPath<Person> path;
private PathBuilder<Person> builder;
@Before
public void setUp() {
this.path = SimpleEntityPathResolver.INSTANCE.createPath(Person.class);
this.builder = new PathBuilder<Person>(path.getType(), path.getMetadata());
}
/**
* @see DATACMNS-525
*/
@Test(expected = IllegalArgumentException.class)
public void toOrderSpecifierThrowsExceptioOnNullPathBuilder() {
QueryDslUtils.toOrderSpecifier(new Sort("firstname"), null);
}
/**
* @see DATACMNS-525
*/
@Test
public void toOrderSpecifierReturnsEmptyArrayWhenSortIsNull() {
assertThat(QueryDslUtils.toOrderSpecifier(null, builder), arrayWithSize(0));
}
/**
* @see DATACMNS-525
*/
@Test
public void toOrderSpecifierConvertsSimpleAscSortCorrectly() {
Sort sort = new Sort(Direction.ASC, "firstname");
OrderSpecifier<?>[] specifiers = QueryDslUtils.toOrderSpecifier(sort, builder);
assertThat(specifiers, IsArrayContainingInOrder.<OrderSpecifier<?>> arrayContaining(QPerson.person.firstname.asc()));
}
/**
* @see DATACMNS-525
*/
@Test
public void toOrderSpecifierConvertsSimpleDescSortCorrectly() {
Sort sort = new Sort(Direction.DESC, "firstname");
OrderSpecifier<?>[] specifiers = QueryDslUtils.toOrderSpecifier(sort, builder);
assertThat(specifiers,
IsArrayContainingInOrder.<OrderSpecifier<?>> arrayContaining(QPerson.person.firstname.desc()));
}
/**
* @see DATACMNS-525
*/
@Test
public void toOrderSpecifierConvertsSortCorrectlyAndRetainsArgumentOrder() {
Sort sort = new Sort(Direction.DESC, "firstname").and(new Sort(Direction.ASC, "age"));
OrderSpecifier<?>[] specifiers = QueryDslUtils.toOrderSpecifier(sort, builder);
assertThat(specifiers, IsArrayContainingInOrder.<OrderSpecifier<?>> arrayContaining(
QPerson.person.firstname.desc(), QPerson.person.age.asc()));
}
/**
* @see DATACMNS-525
*/
@Test
public void toOrderSpecifierConvertsSortWithNullHandlingCorrectly() {
Sort sort = new Sort(new Sort.Order(Direction.DESC, "firstname", NullHandling.NULLS_LAST));
OrderSpecifier<?>[] specifiers = QueryDslUtils.toOrderSpecifier(sort, builder);
assertThat(specifiers,
IsArrayContainingInOrder.<OrderSpecifier<?>> arrayContaining(QPerson.person.firstname.desc().nullsLast()));
}
}

2
template.mf

@ -8,12 +8,14 @@ Import-Package:
sun.reflect;version="0";resolution:=optional sun.reflect;version="0";resolution:=optional
Import-Template: Import-Template:
com.fasterxml.jackson.*;version="${jackson:[=.=.=,+1.0.0)}";resolution:=optional, com.fasterxml.jackson.*;version="${jackson:[=.=.=,+1.0.0)}";resolution:=optional,
com.hazelcast.*;version="0";resolution:=optional,
com.mysema.query.*;version="${querydsl:[=.=.=,+1.0.0)}";resolution:=optional, com.mysema.query.*;version="${querydsl:[=.=.=,+1.0.0)}";resolution:=optional,
com.google.common.*;version="${guava:[=.=.=,+1.0.0)}";resolution:=optional, com.google.common.*;version="${guava:[=.=.=,+1.0.0)}";resolution:=optional,
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional, javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
javax.inject.*;version="[1.0.0,2.0.0)";resolution:=optional, javax.inject.*;version="[1.0.0,2.0.0)";resolution:=optional,
javax.xml.bind.*;version="0";resolution:=optional, javax.xml.bind.*;version="0";resolution:=optional,
javax.xml.transform.*;version="0";resolution:=optional, javax.xml.transform.*;version="0";resolution:=optional,
net.sf.ehcache.*;version="[2.8.0, 4.0.0)";resolution:=optional,
org.springframework.aop.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.aop.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.asm.*;version="${spring:[=.=.=,+1.1.0)}", org.springframework.asm.*;version="${spring:[=.=.=,+1.1.0)}",
org.springframework.beans.*;version="${spring:[=.=.=,+1.1.0)}", org.springframework.beans.*;version="${spring:[=.=.=,+1.1.0)}",

Loading…
Cancel
Save