DATAGEODE-105 - Add ability to filter types de/serialized by the o.s.d.g.mapping.MappingPdxSerializer.
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
[[mapping.entities]]
|
||||
== Entity Mapping
|
||||
|
||||
_Spring Data Geode_ provides support to map entities that will be stored in a Region in the Geode In-Memory Data Grid.
|
||||
_Spring Data for Apache Geode_ provides support to map entities that will be stored in a Region
|
||||
in the Geode In-Memory Data Grid.
|
||||
|
||||
The mapping metadata is defined using annotations on application domain classes just like this:
|
||||
|
||||
.Mapping a domain class to a Geode Region
|
||||
@@ -124,29 +126,256 @@ either as a local cache transaction or a global transaction.
|
||||
[[mapping.pdx-serializer]]
|
||||
== Mapping PDX Serializer
|
||||
|
||||
_Spring Data Geode_ provides a custom
|
||||
_Spring Data for Apache Geode_ provides a custom
|
||||
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/pdx/PdxSerializer.html[PdxSerializer] implementation
|
||||
that uses the mapping information to customize entity serialization. Beyond that, it allows customizing
|
||||
the entity instantiation by using the Spring Data `EntityInstantiator` abstraction. By default the serializer
|
||||
uses a `ReflectionEntityInstantiator` that will use the persistence constructor of the mapped entity
|
||||
(either the default constructor, a singly declared constructor or an explicitly annotated constructor annotated with
|
||||
the `@PersistenceConstructor` annotation).
|
||||
that uses the mapping information to customize entity serialization.
|
||||
|
||||
To provide values for constructor parameters it will read fields with name of the constructor parameters from
|
||||
the supplied http://geode.apache.org/releases/latest/javadoc/org/apache/geode/pdx/PdxReader.html[PdxReader].
|
||||
Beyond that, it also allows customizing entity instantiation by using the Spring Data `EntityInstantiator` abstraction.
|
||||
By default, the serializer uses a `ReflectionEntityInstantiator` that will use the persistence constructor of
|
||||
the mapped entity (either the default constructor, a singly declared constructor or an explicitly annotated constructor
|
||||
annotated with the `@PersistenceConstructor` annotation).
|
||||
|
||||
.Using @Value on entity constructor parameters
|
||||
To provide arguments for constructor parameters, the serializer will read fields with the named constructor parameter,
|
||||
explicitly specified using Spring's `@Value` annotation, from the supplied
|
||||
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/pdx/PdxReader.html[PdxReader].
|
||||
|
||||
.Using `@Value` on entity constructor parameters
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
|
||||
public Person(@Value("#root.foo") String firstname, @Value("bean") String lastname) {
|
||||
public Person(@Value("#root.foo") String firstName, @Value("bean") String lastName) {
|
||||
// …
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
An entity class annotated in this way will have the field `foo` read from the `PdxReader` and passed to the constructor
|
||||
parameter value for `firstname`. The value for `lastname` will be the _Spring_ bean with the name `bean`.
|
||||
An entity class annotated in this way will have the field `foo` read from the `PdxReader` and passed as the value
|
||||
for the constructor parameter, `firstname`. The value for `lastName` will be a _Spring_ bean with the name `bean`.
|
||||
|
||||
In addition to the custom instantiation logic and strategy provided by `EntityInstantiators`
|
||||
the `MappingPdxSerializer` also provides capabilities above and beyond even Apache Geode's own
|
||||
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/pdx/ReflectionBasedAutoSerializer.html[`ReflectionBasedAutoSerializer`].
|
||||
|
||||
While Apache Geode's `ReflectionBasedAutoSerializer` conveniently uses Java Reflection to populate entities as well as
|
||||
use _Regular Expressions_ to identify types that should be handled (de/serialized) by the `ReflectionBasedAutoSerializer`,
|
||||
it cannot, unlike `MappingPdxSerializer`, perform the following:
|
||||
|
||||
1. Register custom `PdxSerializer` objects per entity field/property names and/or types.
|
||||
2. Conveniently identifies ID properties.
|
||||
3. Automatically handles *read-only* properties.
|
||||
4. Automatically handles *transient* properties.
|
||||
5. Allows more robust *type filtering* in a `null`-safe manner (e.g. not limited to only expressing types via Regex).
|
||||
|
||||
We now explore each feature of the `MappingPdxSerializer` in a bit more detail.
|
||||
|
||||
[[mapping.pdx-serializer.custom-serialization]]
|
||||
=== Custom PdxSerializer Registration
|
||||
|
||||
The `MappingPdxSerializer` gives you the ability to register custom `PdxSerializers` based on an entity's
|
||||
field/property names and/or types.
|
||||
|
||||
For instance, suppose you have defined an entity type modeling a `User` as...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
package example.app.auth.model;
|
||||
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private Password password;
|
||||
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
While the `User's` "name" probably does not require any special logic to serialize the value for name, serializing
|
||||
the `Password` might require additional logic in order to handle the sensitive nature of the field or property.
|
||||
|
||||
Perhaps you want to protect the password when sending the value over the network, between a client and a server,
|
||||
and you only want to store the _Salted Hash_. When using the `MappingPdxSerializer` you can register
|
||||
a custom `PdxSerializer` to handle the `User's` `Password`, like so...
|
||||
|
||||
.Registering custom `PdxSerializers` by POJO field/property type
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Map<?, PdxSerializer> customPdxSerializers = new HashMap<>();
|
||||
|
||||
customPdxSerializers.put(Password.class, new SaltedHashPasswordPdxSerializer());
|
||||
|
||||
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
|
||||
----
|
||||
|
||||
After registering the application-defined `SaltedHashPasswordPdxSerializer` instance with the `Password`
|
||||
application domain model type, the `MappingPdxSerializer` will consult the custom `PdxSerializer` to
|
||||
de/serialize *all* `Password` objects regardless of the containing object (e.g. `User`).
|
||||
|
||||
However, suppose you only want to customize the serialization of `Passwords` on `User` objects, specifically.
|
||||
Then, you can register the custom `PdxSerializer` for the `User` type only by specifying the fully-qualified
|
||||
name of the `Class's` field/property. For example:
|
||||
|
||||
.Registering custom `PdxSerializers` by POJO field/property name
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Map<?, PdxSerializer> customPdxSerializers = new HashMap<>();
|
||||
|
||||
customPdxSerializers.put("example.app.auth.model.User.password", new SaltedHashPasswordPdxSerializer());
|
||||
|
||||
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
|
||||
----
|
||||
|
||||
Notice the use of the fully-qualified field/propety name (i.e. "example.app.auth.model.User.password")
|
||||
as the custom `PdxSerializer` registration key.
|
||||
|
||||
NOTE: You could construct the registration key using a more logical code snippet, such as:
|
||||
`User.class.getName().concat(".password");` This is recommended over the example shown above. The example was simply
|
||||
trying to be very explicit in the semantics of registration.
|
||||
|
||||
[[mapping.pdx-serializer.id-properties]]
|
||||
=== Mapping ID Properties
|
||||
|
||||
Like Apache Geode's `ReflectionBasedAutoSerializer`, SDG's `MappingPdxSerializer` is also able to determine
|
||||
the identifier of the entity. However, `MappingPdxSerializer` does so by using Spring Data's mapping meta-data,
|
||||
specifically by finding the entity property designated as the identifier using the
|
||||
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/Id.html[`@Id`] Spring Data annotation.
|
||||
|
||||
For example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class Customer {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
In this case, the `Customer's` `id` field will be marked as the identifier field in the PDX type meta-data using
|
||||
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/pdx/PdxWriter.html#markIdentityField-java.lang.String-[`PdxWriter.markIdentifierField(:String)`]
|
||||
when the `PdxSerializer.toData(..)` method is called during serialization.
|
||||
|
||||
[[mapping.pdx-serializer.read-only-properties]]
|
||||
=== Mapping Read-only Properties
|
||||
|
||||
What happens when your entity defines a read-only property?
|
||||
|
||||
First, it is important to understand what a "read-only" property is. If you define a POJO following the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans]
|
||||
specification (as Spring does), and you have defined a POJO with some read-only property as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
package example;
|
||||
|
||||
class ApplicationDomainType {
|
||||
|
||||
private AnotherType readOnly;
|
||||
|
||||
public AnotherType getReadOnly() [
|
||||
this.readOnly;
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Then the `readOnly` property is "read-only" because it does not provide a setter method; it only has a getter method.
|
||||
In this case, the `readOnly` property (not to be confused with the `readOnly` `DomainType` field)
|
||||
is considered "read-only".
|
||||
|
||||
As such, the `MappingPdxSerializer` will not try to write this value back when populating the instance of `DomainType`
|
||||
in the `PdxSerializer.fromData(:Class<?>, :PdxReader)` method.
|
||||
|
||||
This is useful in situations where you might be returning a view or projection of some entity type and you only want
|
||||
to write state that is writable. Perhaps the view or projection of the entity is based on authorization or some other
|
||||
criteria. The point is, you can leverage this feature as is appropriate for your application use cases and requirements.
|
||||
If you want the field/property to always be written then simply define a setter.
|
||||
|
||||
[[mapping.pdx-serializer.transient-properties]]
|
||||
=== Mapping Transient Properties
|
||||
|
||||
Likewise, what happens when your entity defines `transient` properties?
|
||||
|
||||
You would expect the `transient` fields/properties of your entity not to be serialized to the stream of PDX bytes
|
||||
when serializing entity. And, that is exactly what happens, unlike Apache Geode's own
|
||||
`ReflectionBasedAutoSerializer`, which serializes everything accessible from the object via _Java Reflection_.
|
||||
|
||||
The `MappingPdxSerializer` will not serialize any fields or properties which are qualified as transient either using
|
||||
Java's `transient` keyword (in the case of fields) or when using the
|
||||
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/Transient.html[`@Transient`]
|
||||
Spring Data annotation on either fields or properties.
|
||||
|
||||
For example, if you defined an enity with transient fields and properties, like so...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
package example;
|
||||
|
||||
class Process {
|
||||
|
||||
private transient int id;
|
||||
|
||||
private File workingDirectory;
|
||||
|
||||
private String name;
|
||||
|
||||
private Type type;
|
||||
|
||||
@Transient
|
||||
public String getHostname() {
|
||||
...
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Neither the `Process` `id` field nor the readable `hostname` property will be written to the PDX serialized bytes.
|
||||
|
||||
[[mapping.pdx-serializer.type-filtering]]
|
||||
=== Filtering by Class types
|
||||
|
||||
Similar to Apache Geode's `ReflectionBasedAutoSerializer`, SDG's `MappingPdxSerializer` allows a user to filter
|
||||
the types of objects that the `MappingPdxSerializer` will handle, i.e. de/serialize.
|
||||
|
||||
However, unlike Apache Geode's `ReflectionBasedAutoSerializer`, which uses complex _Regular Expressions_ to express
|
||||
which types the serializer will handle, SDG's `MappingPdxSerializer` uses the much more robust
|
||||
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html[`java.util.function.Predicate`] interface
|
||||
and API to express type matching criteria.
|
||||
|
||||
Plus, if you feel strongly about using _Regular Expressions_, then you can always implement a `Predicate` using
|
||||
_Java's_ https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html[_Regular Expression_ support].
|
||||
|
||||
The nice part about Java's `Predicate` interface is that you can compose `Predicates` using the convenient
|
||||
and appropriate API:
|
||||
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#and-java.util.function.Predicate-[`and(:Predicate)`],
|
||||
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#or-java.util.function.Predicate-[`or(:Predicate)`]
|
||||
and https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#negate--[`negate()`].
|
||||
|
||||
For example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
Predicate<Class<?>> customerTypes =
|
||||
type -> Customer.class.getPackage().getName().startsWith(type.getName());
|
||||
|
||||
Predicate typeFilters = customerTypes
|
||||
.or(type -> User.class.isAssignble(type)) // Include User sub-types (e.g. Admin, Guest, etc)
|
||||
.and(type -> !Reference.class.getPackage(type.getPackage()); // Exclude all Reference types
|
||||
|
||||
mappingPdxSerializer.setTypeFilters(typeFilters);
|
||||
|
||||
----
|
||||
|
||||
NOTE: In addition to setting your own type filtering `Predicates`, SDG's `MappingPdxSerializer` now automatically
|
||||
registers pre-canned `Predicates` that filters types from the `org.apache.geode` package along with `null` objects
|
||||
when calling `PdxSerializer.toData(:Object, :PdxWriter)` or `null` `Class` types when calling
|
||||
`PdxSerializer.fromData(:Class<?>, :PdxReader)` methods.
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.springframework.data.gemfire.mapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.pdx.PdxReader;
|
||||
import org.apache.geode.pdx.PdxSerializer;
|
||||
@@ -30,6 +32,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.gemfire.util.Filter;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
@@ -67,42 +70,62 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAware {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private EntityInstantiators entityInstantiators;
|
||||
|
||||
private final GemfireMappingContext mappingContext;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private Map<?, PdxSerializer> customPdxSerializers;
|
||||
|
||||
// TODO: decide what to do with this; SpELContext is not used
|
||||
private SpELContext context;
|
||||
protected static final String COM_GEMSTONE_GEMFIRE_PACKAGE_NAME = "com.gemstone.gemfire";
|
||||
protected static final String ORG_APACHE_GEODE_PACKAGE_NAME = "org.apache.geode";
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link MappingPdxSerializer} initialized with
|
||||
* a provided {@link GemfireMappingContext} and default {@link ConversionService}.
|
||||
*
|
||||
* @return a new instance of {@link MappingPdxSerializer}.
|
||||
* @see #create(GemfireMappingContext, ConversionService)
|
||||
* @see #newMappingContext()
|
||||
* @see #newConversionService()
|
||||
*/
|
||||
public static MappingPdxSerializer newMappingPdxSerializer() {
|
||||
return create(newMappingContext(), newConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link MappingPdxSerializer} initialized with
|
||||
* the given {@link ConversionService} and a provided {@link GemfireMappingContext}.
|
||||
*
|
||||
* @param conversionService {@link ConversionService} used to convert persistent values to entity properties.
|
||||
* @return a new instance of {@link MappingPdxSerializer} initialized with the given {@link ConversionService}.
|
||||
* @see org.springframework.core.convert.ConversionService
|
||||
* @see #create(GemfireMappingContext, ConversionService)
|
||||
* @see #newMappingContext()
|
||||
*/
|
||||
public static MappingPdxSerializer create(@Nullable ConversionService conversionService) {
|
||||
return create(newMappingContext(), conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link MappingPdxSerializer} initialized with
|
||||
* the given {@link GemfireMappingContext mapping context} supplying entity mapping meta-data,
|
||||
* using a provided, default {@link ConversionService}.
|
||||
*
|
||||
* @param mappingContext {@link GemfireMappingContext} used to supply entity mapping meta-data.
|
||||
* @return a new instance of {@link MappingPdxSerializer} initialized with
|
||||
* the given {@link GemfireMappingContext mapping context}.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
|
||||
* @see #create(GemfireMappingContext, ConversionService)
|
||||
* @see #newConversionService()
|
||||
*/
|
||||
public static MappingPdxSerializer create(@Nullable GemfireMappingContext mappingContext) {
|
||||
return create(mappingContext, newConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of the {@link MappingPdxSerializer} initialized with
|
||||
* Factory method used to construct a new instance of {@link MappingPdxSerializer} initialized with
|
||||
* the given {@link GemfireMappingContext mapping context} and {@link ConversionService conversion service}.
|
||||
*
|
||||
* If either the {@link GemfireMappingContext mapping context} or the {@link ConversionService conversion service}
|
||||
* are {@literal null}, then this factory method will provide default instances for each.
|
||||
*
|
||||
* @param mappingContext {@link GemfireMappingContext} used by the {@link MappingPdxSerializer} to map
|
||||
* between application domain object types and PDX serialized bytes based on the entity mapping meta-data.
|
||||
* @param conversionService {@link ConversionService} used by the {@link MappingPdxSerializer} to convert
|
||||
* PDX serialized data to application object property types.
|
||||
* @param mappingContext {@link GemfireMappingContext} used to map between application domain model object types
|
||||
* and PDX serialized bytes based on the entity's mapping meta-data.
|
||||
* @param conversionService {@link ConversionService} used to convert persistent values to entity properties.
|
||||
* @return an initialized instance of the {@link MappingPdxSerializer}.
|
||||
* @see org.springframework.core.convert.ConversionService
|
||||
* @see org.springframework.data.gemfire.mapping.MappingPdxSerializer
|
||||
@@ -161,6 +184,23 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
return mappingContext != null ? mappingContext : newMappingContext();
|
||||
}
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private EntityInstantiators entityInstantiators;
|
||||
|
||||
private final GemfireMappingContext mappingContext;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private Map<?, PdxSerializer> customPdxSerializers;
|
||||
|
||||
private Predicate<Class<?>> typeFilters = TypeFilters.EXCLUDE_NULL_TYPES
|
||||
.and(TypeFilters.EXCLUDE_COM_GEMSTONE_GEMFIRE_TYPES)
|
||||
.and(TypeFilters.EXCLUDE_ORG_APACHE_GEODE_TYPES);
|
||||
|
||||
// TODO: decide what to do with this; SpELContext is not used
|
||||
private SpELContext spelContext;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link MappingPdxSerializer} using a default {@link GemfireMappingContext}
|
||||
* and {@link DefaultConversionService}.
|
||||
@@ -187,14 +227,14 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
*/
|
||||
public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext is required");
|
||||
Assert.notNull(conversionService, "ConversionService is required");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.conversionService = conversionService;
|
||||
this.entityInstantiators = new EntityInstantiators();
|
||||
this.customPdxSerializers = Collections.emptyMap();
|
||||
this.context = new SpELContext(PdxReaderPropertyAccessor.INSTANCE);
|
||||
this.spelContext = new SpELContext(PdxReaderPropertyAccessor.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +245,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.context = new SpELContext(this.context, applicationContext);
|
||||
this.spelContext = new SpELContext(this.spelContext, applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +272,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
*/
|
||||
public void setCustomPdxSerializers(@NonNull Map<?, PdxSerializer> customPdxSerializers) {
|
||||
|
||||
Assert.notNull(customPdxSerializers, "Custom PdxSerializers are required");
|
||||
Assert.notNull(customPdxSerializers, "Custom PdxSerializers must not be null");
|
||||
|
||||
this.customPdxSerializers = customPdxSerializers;
|
||||
}
|
||||
@@ -324,7 +364,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
*/
|
||||
public void setGemfireInstantiators(@NonNull EntityInstantiators entityInstantiators) {
|
||||
|
||||
Assert.notNull(entityInstantiators, "EntityInstantiators are required");
|
||||
Assert.notNull(entityInstantiators, "EntityInstantiators must not be null");
|
||||
|
||||
this.entityInstantiators = entityInstantiators;
|
||||
}
|
||||
@@ -409,12 +449,53 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity
|
||||
* @see #getMappingContext()
|
||||
*/
|
||||
protected GemfirePersistentEntity<?> getPersistentEntity(Class<?> entityType) {
|
||||
protected GemfirePersistentEntity<?> getPersistentEntity(@NonNull Class<?> entityType) {
|
||||
return getMappingContext().getPersistentEntity(entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Predicate type filters} used to filter {@link Class types} serializable
|
||||
* by this {@link MappingPdxSerializer PDX serializer}.
|
||||
*
|
||||
* This operation is null-safe and rather than overriding the existing {@link Predicate type filters},
|
||||
* this set operation combines the given {@link Predicate type filters} with
|
||||
* the exiting {@link Predicate type filters} joined by {@literal and}.
|
||||
*
|
||||
* @param typeFilters {@link Predicate type filters} used to to filter {@link Class type} serializable
|
||||
* by this {@link MappingPdxSerializer PDX serializer}.
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public void setTypeFilters(@Nullable Predicate<Class<?>> typeFilters) {
|
||||
this.typeFilters = typeFilters != null ? this.typeFilters.and(typeFilters) : this.typeFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Predicate type filters} used to filter {@link Class types} serializable
|
||||
* by this {@link MappingPdxSerializer PdxSerializer}.
|
||||
*
|
||||
* @return the resolved {@link Predicate type filter}.
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected Predicate<Class<?>> getTypeFilters() {
|
||||
return this.typeFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromData(Class<?> type, PdxReader reader) {
|
||||
return getTypeFilters().test(type) ? doFromData(type, reader) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a set of PDX serialized bytes to an {@link Object} of the specified {@link Class type}.
|
||||
*
|
||||
* @param type desired {@link Class type} of the {@link Object}.
|
||||
* @param reader {@link PdxReader} used to access the PDX bytes to convert.
|
||||
* @return an {@link Object} of the specified {@link Class type} converted from the PDX bytes.
|
||||
* @see org.apache.geode.pdx.PdxReader
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
Object doFromData(Class<?> type, PdxReader reader) {
|
||||
|
||||
GemfirePersistentEntity<?> entity = getPersistentEntity(type);
|
||||
|
||||
@@ -453,8 +534,8 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
catch (Exception cause) {
|
||||
throw new MappingException(String.format(
|
||||
"While setting value [%1$s] of property [%2$s] for entity of type [%3$s] from PDX%4$s",
|
||||
value, persistentProperty.getName(), type, (customPdxSerializer != null ?
|
||||
String.format(" using custom PdxSerializer [%1$s]", customPdxSerializer) : "")), cause);
|
||||
value, persistentProperty.getName(), type, (customPdxSerializer != null ?
|
||||
String.format(" using custom PdxSerializer [%1$s]", customPdxSerializer) : "")), cause);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -462,7 +543,21 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
/**
|
||||
* Determines whether the {@link PersistentProperty persistent property}
|
||||
* of the given {@link PersistentEntity entity } is writable.
|
||||
*
|
||||
* The {@link PersistentProperty persistent property} is considered {@literal writable} if the property
|
||||
* is not a constructor parameter of the {@link PersistentEntity entity's} {@link Class type}, the property
|
||||
* has a {@literal setter} method and the property is not {@literal transient}.
|
||||
*
|
||||
* @param entity {@link GemfirePersistentEntity} containing the {@link GemfirePersistentProperty property}.
|
||||
* @param persistentProperty {@link GemfirePersistentProperty} to evaluate.
|
||||
* @return a boolean value indicating whether the {@link PersistentProperty persistent property}
|
||||
* of the given {@link PersistentEntity entity } is writable.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty
|
||||
*/
|
||||
boolean isWritable(GemfirePersistentEntity<?> entity, GemfirePersistentProperty persistentProperty) {
|
||||
|
||||
return !entity.isConstructorArgument(persistentProperty)
|
||||
@@ -471,12 +566,26 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean toData(Object value, PdxWriter writer) {
|
||||
return getTypeFilters().test(resolveType(value)) && doToData(value, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link Object} into a stream of PDX bytes.
|
||||
*
|
||||
* @param value {@link Object} to convert.
|
||||
* @param writer {@link PdxWriter} used to stream the given {@link Object} into a stream of PDX bytes.
|
||||
* @return a boolean value indicating whether this {@link MappingPdxSerializer PDX serializer} was able to
|
||||
* write the given {@link Object} as a stream of PDX bytes.
|
||||
* @see org.apache.geode.pdx.PdxWriter
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
boolean doToData(Object value, PdxWriter writer) {
|
||||
|
||||
GemfirePersistentEntity<?> entity = getPersistentEntity(value);
|
||||
|
||||
// Entity will be null for simple types
|
||||
// Entity will be null for simple types (e.g. int, Long, String, etc).
|
||||
if (entity != null) {
|
||||
|
||||
PersistentPropertyAccessor propertyAccessor =
|
||||
@@ -528,12 +637,67 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
/**
|
||||
* Determines whether the given {@link PersistentProperty persistent property} is readable.
|
||||
*
|
||||
* The {@link PersistentProperty persistent property} is considered {@literal readable}
|
||||
* if the property is not {@literal transient}.
|
||||
*
|
||||
* @param persistentProperty {@link GemfirePersistentProperty} to evaluate.
|
||||
* @return a boolean value indicating whether the {@link PersistentProperty persistent property}
|
||||
* is readable.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty
|
||||
*/
|
||||
boolean isReadable(GemfirePersistentProperty persistentProperty) {
|
||||
return !persistentProperty.isTransient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Class type} of the given {@link Object}.
|
||||
*
|
||||
* @param obj {@link Object} to evaluate.
|
||||
* @return the {@link Class type} of the given {@link Object}.
|
||||
* @see java.lang.Object#getClass()
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
@Nullable
|
||||
Class<?> resolveType(@Nullable Object obj) {
|
||||
return obj != null ? obj.getClass() : null;
|
||||
}
|
||||
|
||||
public enum TypeFilters implements Filter<Class<?>> {
|
||||
|
||||
EXCLUDE_COM_GEMSTONE_GEMFIRE_TYPES {
|
||||
|
||||
@Override
|
||||
public boolean accept(@Nullable Class<?> obj) {
|
||||
|
||||
return Optional.ofNullable(obj)
|
||||
.filter(type -> !type.getPackage().getName().startsWith(COM_GEMSTONE_GEMFIRE_PACKAGE_NAME))
|
||||
.isPresent();
|
||||
}
|
||||
},
|
||||
|
||||
EXCLUDE_NULL_TYPES {
|
||||
|
||||
@Override
|
||||
public boolean accept(@Nullable Class<?> obj) {
|
||||
return obj != null;
|
||||
}
|
||||
},
|
||||
|
||||
EXCLUDE_ORG_APACHE_GEODE_TYPES {
|
||||
|
||||
@Override
|
||||
public boolean accept(Class<?> obj) {
|
||||
|
||||
return Optional.ofNullable(obj)
|
||||
.filter(type -> !type.getPackage().getName().startsWith(ORG_APACHE_GEODE_PACKAGE_NAME))
|
||||
.isPresent();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2018 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.gemfire.util;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Filter} interface defines a contract for filtering {@link Object objects}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @param <T> {@link Class type} of {@link Object objects} being filtered.
|
||||
* @see java.util.function.Predicate
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface Filter<T> extends Predicate<T> {
|
||||
|
||||
/**
|
||||
* Evaluates the given {@link Object} and determines whether the {@link Object} is accepted
|
||||
* based on the filter criteria.
|
||||
*
|
||||
* @param obj {@link Object} to filter.
|
||||
* @return a boolean value indicating whether this {@link Filter} accepts the given {@link Object}
|
||||
* based on the filter criteria.
|
||||
*/
|
||||
boolean accept(@Nullable T obj);
|
||||
|
||||
/**
|
||||
* Tests whether the given {@link Object} matches the criteria defined by this {@link Filter}.
|
||||
*
|
||||
* @param obj {@link Object} to test.
|
||||
* @return a boolean value indicating whether the given {@link Object} matches the criteria
|
||||
* defined by this {@link Filter}.
|
||||
* @see #accept(Object)
|
||||
*/
|
||||
@Override
|
||||
default boolean test(T obj) {
|
||||
return accept(obj);
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,17 @@
|
||||
package org.springframework.data.gemfire.mapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.atMost;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
@@ -48,8 +52,14 @@ import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.gemfire.repository.sample.Account;
|
||||
import org.springframework.data.gemfire.repository.sample.Address;
|
||||
import org.springframework.data.gemfire.repository.sample.Customer;
|
||||
import org.springframework.data.gemfire.repository.sample.Person;
|
||||
import org.springframework.data.gemfire.repository.sample.Programmer;
|
||||
import org.springframework.data.gemfire.repository.sample.RootUser;
|
||||
import org.springframework.data.gemfire.repository.sample.User;
|
||||
import org.springframework.data.gemfire.test.model.Gender;
|
||||
import org.springframework.data.gemfire.test.support.MapBuilder;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -79,13 +89,13 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
ConversionService conversionService;
|
||||
|
||||
@Mock
|
||||
EntityInstantiator mockInstantiator;
|
||||
|
||||
GemfireMappingContext mappingContext;
|
||||
|
||||
MappingPdxSerializer pdxSerializer;
|
||||
|
||||
@Mock
|
||||
EntityInstantiator mockInstantiator;
|
||||
|
||||
@Mock
|
||||
PdxReader mockReader;
|
||||
|
||||
@@ -97,7 +107,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
this.conversionService = new GenericConversionService();
|
||||
this.mappingContext = new GemfireMappingContext();
|
||||
this.pdxSerializer = new MappingPdxSerializer(this.mappingContext, this.conversionService);
|
||||
this.pdxSerializer = spy(new MappingPdxSerializer(this.mappingContext, this.conversionService));
|
||||
}
|
||||
|
||||
private String toFullyQualifiedPropertyName(PersistentProperty<?> persistentProperty) {
|
||||
@@ -138,7 +148,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("ConversionService is required");
|
||||
assertThat(expected).hasMessage("ConversionService must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
@@ -153,7 +163,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("MappingContext is required");
|
||||
assertThat(expected).hasMessage("MappingContext must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
@@ -227,7 +237,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Custom PdxSerializers are required");
|
||||
assertThat(expected).hasMessage("Custom PdxSerializers must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
@@ -236,7 +246,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForProperty() {
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsPdxSerializerForProperty() {
|
||||
|
||||
PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
|
||||
PdxSerializer mockPropertySerializer = mock(PdxSerializer.class);
|
||||
@@ -257,7 +267,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForPropertyName() {
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsPdxSerializerForPropertyName() {
|
||||
|
||||
PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
|
||||
PdxSerializer mockTypedSerializer = mock(PdxSerializer.class);
|
||||
@@ -276,7 +286,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForPropertyType() {
|
||||
public void getCustomPdxSerializerForMappedPersistentPropertyReturnsPdxSerializerForPropertyType() {
|
||||
|
||||
PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
|
||||
PdxSerializer mockTypedSerializer = mock(PdxSerializer.class);
|
||||
@@ -309,7 +319,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getCustomSerializerForMappedType() {
|
||||
public void getCustomSerializerForMappedTypeReturnsPdxSerializer() {
|
||||
|
||||
PdxSerializer mockPdxSerializer = mock(PdxSerializer.class);
|
||||
|
||||
@@ -361,7 +371,7 @@ public class MappingPdxSerializerUnitTests {
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("EntityInstantiators are required");
|
||||
assertThat(expected).hasMessage("EntityInstantiators must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
@@ -627,7 +637,48 @@ public class MappingPdxSerializerUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataSerializesApplicationDomainObjectToPdx() {
|
||||
public void fromDataWithTypeFilterAcceptsApplicationDomainTypes() {
|
||||
|
||||
this.pdxSerializer.setTypeFilters(type -> User.class.getPackage().equals(type.getPackage()));
|
||||
|
||||
doReturn("test").when(this.pdxSerializer).doFromData(any(Class.class), any(PdxReader.class));
|
||||
|
||||
assertThat(this.pdxSerializer.fromData(Account.class, this.mockReader)).isEqualTo("test");
|
||||
assertThat(this.pdxSerializer.fromData(Customer.class, this.mockReader)).isEqualTo("test");
|
||||
assertThat(this.pdxSerializer.fromData(Programmer.class, this.mockReader)).isEqualTo("test");
|
||||
assertThat(this.pdxSerializer.fromData(User.class, this.mockReader)).isEqualTo("test");
|
||||
assertThat(this.pdxSerializer.fromData(org.springframework.data.gemfire.test.model.Person.class, this.mockReader)).isNull();
|
||||
assertThat(this.pdxSerializer.fromData(null, this.mockReader)).isNull();
|
||||
|
||||
verify(this.pdxSerializer, times(1)).doFromData(eq(Account.class), eq(this.mockReader));
|
||||
verify(this.pdxSerializer, times(1)).doFromData(eq(Customer.class), eq(this.mockReader));
|
||||
verify(this.pdxSerializer, times(1)).doFromData(eq(Programmer.class), eq(this.mockReader));
|
||||
verify(this.pdxSerializer, times(1)).doFromData(eq(User.class), eq(this.mockReader));
|
||||
verify(this.pdxSerializer, never()).doFromData(isNull(), eq(this.mockReader));
|
||||
verify(this.pdxSerializer, never())
|
||||
.doFromData(eq(org.springframework.data.gemfire.test.model.Person.class), eq(this.mockReader));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDataWithTypeFilterFiltersApacheGeodeTypeReturnsNull() {
|
||||
assertThat(this.pdxSerializer.fromData(org.apache.geode.cache.EntryEvent.class, this.mockReader)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDataWithTypeFilterFiltersApplicationDomainTypeReturnsNull() {
|
||||
|
||||
this.pdxSerializer.setTypeFilters(type -> !ApplicationDomainType.class.equals(type));
|
||||
|
||||
assertThat(this.pdxSerializer.fromData(ApplicationDomainType.class, this.mockReader)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDataWithTypeFilterFiltersNullTypeReturnsNull() {
|
||||
assertThat(this.pdxSerializer.fromData(null, this.mockReader)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataSerializesApplicationDomainObjectToPdxBytes() {
|
||||
|
||||
Address address = new Address();
|
||||
|
||||
@@ -708,4 +759,51 @@ public class MappingPdxSerializerUnitTests {
|
||||
verify(this.mockWriter, never()).markIdentityField(anyString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataFiltersNullTypeReturnsFalse() {
|
||||
assertThat(this.pdxSerializer.toData(null, this.mockWriter)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataFiltersApacheGeodeTypeReturnsFalse() {
|
||||
assertThat(this.pdxSerializer.toData(mock(org.apache.geode.cache.EntryEvent.class), this.mockWriter)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataFiltersApplicationDomainTypeReturnsFalse() {
|
||||
|
||||
this.pdxSerializer.setTypeFilters(type -> !ApplicationDomainType.class.equals(type));
|
||||
|
||||
assertThat(this.pdxSerializer.toData(new ApplicationDomainType(), this.mockWriter)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDataAcceptsApplicationDomainObjectTypeReturnsTrue() {
|
||||
|
||||
org.springframework.data.gemfire.test.model.Person jonDoe =
|
||||
new org.springframework.data.gemfire.test.model.Person("Jon", "Doe",
|
||||
null, Gender.MALE);
|
||||
|
||||
this.pdxSerializer.setTypeFilters(type -> User.class.getPackage().equals(type.getPackage()));
|
||||
|
||||
doReturn(true).when(this.pdxSerializer).doToData(any(), any(PdxWriter.class));
|
||||
|
||||
assertThat(this.pdxSerializer.toData(new Account(1L), this.mockWriter)).isTrue();
|
||||
assertThat(this.pdxSerializer.toData(new Customer(1L), this.mockWriter)).isTrue();
|
||||
assertThat(this.pdxSerializer.toData(new Programmer("jxblum"), this.mockWriter)).isTrue();
|
||||
assertThat(this.pdxSerializer.toData(new RootUser("jxblum"), this.mockWriter)).isTrue();
|
||||
assertThat(this.pdxSerializer.toData(null, this.mockWriter)).isFalse();
|
||||
assertThat(this.pdxSerializer.toData(jonDoe, this.mockWriter)).isFalse();
|
||||
|
||||
verify(this.pdxSerializer, times(1)).doToData(isA(Account.class), eq(this.mockWriter));
|
||||
verify(this.pdxSerializer, times(1)).doToData(isA(Customer.class), eq(this.mockWriter));
|
||||
verify(this.pdxSerializer, times(1)).doToData(isA(Programmer.class), eq(this.mockWriter));
|
||||
verify(this.pdxSerializer, times(1)).doToData(isA(RootUser.class), eq(this.mockWriter));
|
||||
verify(this.pdxSerializer, never()).doToData(isNull(), eq(this.mockWriter));
|
||||
verify(this.pdxSerializer, never()).doToData(isA(jonDoe.getClass()), eq(this.mockWriter));
|
||||
}
|
||||
|
||||
private static class ApplicationDomainType { }
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2018 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.gemfire.util;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Filter}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.util.Filter
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class FilterUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void andIsCorrect() {
|
||||
|
||||
Filter mockFilterOne = mock(Filter.class);
|
||||
Filter mockFilterTwo = mock(Filter.class);
|
||||
Filter mockFilterThree = mock(Filter.class);
|
||||
Filter mockFilterFour = mock(Filter.class);
|
||||
|
||||
when(mockFilterOne.accept(any())).thenReturn(false);
|
||||
when(mockFilterOne.and(any())).thenCallRealMethod();
|
||||
when(mockFilterOne.test(any())).thenCallRealMethod();
|
||||
when(mockFilterTwo.accept(any())).thenReturn(true);
|
||||
when(mockFilterTwo.and(any())).thenCallRealMethod();
|
||||
when(mockFilterTwo.test(any())).thenCallRealMethod();
|
||||
when(mockFilterThree.accept(any())).thenReturn(false);
|
||||
when(mockFilterThree.and(any())).thenCallRealMethod();
|
||||
when(mockFilterThree.test(any())).thenCallRealMethod();
|
||||
when(mockFilterFour.accept(any())).thenReturn(true);
|
||||
when(mockFilterFour.and(any())).thenCallRealMethod();
|
||||
when(mockFilterFour.test(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockFilterOne.and(mockFilterTwo).test("test")).isFalse();
|
||||
assertThat(mockFilterOne.and(mockFilterThree).test("test")).isFalse();
|
||||
assertThat(mockFilterTwo.and(mockFilterThree).test("test")).isFalse();
|
||||
assertThat(mockFilterTwo.and(mockFilterFour).test("test")).isTrue();
|
||||
|
||||
verify(mockFilterOne, times(2)).accept(eq("test"));
|
||||
verify(mockFilterTwo, times(2)).accept(eq("test"));
|
||||
verify(mockFilterThree, times(1)).accept(eq("test"));
|
||||
verify(mockFilterFour, times(1)).accept(eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void orIsCorrect() {
|
||||
|
||||
Filter mockFilterOne = mock(Filter.class);
|
||||
Filter mockFilterTwo = mock(Filter.class);
|
||||
Filter mockFilterThree = mock(Filter.class);
|
||||
Filter mockFilterFour = mock(Filter.class);
|
||||
|
||||
when(mockFilterOne.accept(any())).thenReturn(false);
|
||||
when(mockFilterOne.or(any())).thenCallRealMethod();
|
||||
when(mockFilterOne.test(any())).thenCallRealMethod();
|
||||
when(mockFilterTwo.accept(any())).thenReturn(true);
|
||||
when(mockFilterTwo.or(any())).thenCallRealMethod();
|
||||
when(mockFilterTwo.test(any())).thenCallRealMethod();
|
||||
when(mockFilterThree.accept(any())).thenReturn(false);
|
||||
when(mockFilterThree.or(any())).thenCallRealMethod();
|
||||
when(mockFilterThree.test(any())).thenCallRealMethod();
|
||||
when(mockFilterFour.accept(any())).thenReturn(true);
|
||||
when(mockFilterFour.or(any())).thenCallRealMethod();
|
||||
when(mockFilterFour.test(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockFilterOne.or(mockFilterTwo).test("test")).isTrue();
|
||||
assertThat(mockFilterOne.or(mockFilterThree).test("test")).isFalse();
|
||||
assertThat(mockFilterTwo.or(mockFilterThree).test("test")).isTrue();
|
||||
assertThat(mockFilterTwo.or(mockFilterFour).test("test")).isTrue();
|
||||
|
||||
verify(mockFilterOne, times(2)).accept(eq("test"));
|
||||
verify(mockFilterTwo, times(3)).accept(eq("test"));
|
||||
verify(mockFilterThree, times(1)).accept(eq("test"));
|
||||
verify(mockFilterFour, never()).accept(eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void negateReturnsFalseForTrue() {
|
||||
|
||||
Filter<Object> mockFilter = mock(Filter.class);
|
||||
|
||||
when(mockFilter.accept(any())).thenReturn(true);
|
||||
when(mockFilter.negate()).thenCallRealMethod();
|
||||
when(mockFilter.test(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockFilter.negate().test("test")).isFalse();
|
||||
|
||||
verify(mockFilter, times(1)).accept(eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void predicateTestCallsFilterAcceptReturnsTrue() {
|
||||
|
||||
Filter<Object> mockFilter = mock(Filter.class);
|
||||
|
||||
when(mockFilter.accept(any())).thenReturn(true);
|
||||
when(mockFilter.test(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockFilter.test("test")).isTrue();
|
||||
|
||||
verify(mockFilter, times(1)).accept(eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void predicateTestCallsFilterAcceptReturnsFalse() {
|
||||
|
||||
Filter<Object> mockFilter = mock(Filter.class);
|
||||
|
||||
when(mockFilter.accept(any())).thenReturn(false);
|
||||
when(mockFilter.test(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockFilter.test("test")).isFalse();
|
||||
|
||||
verify(mockFilter, times(1)).accept(eq("test"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user