DATAGEODE-105 - Add ability to filter types de/serialized by the o.s.d.g.mapping.MappingPdxSerializer.

This commit is contained in:
John Blum
2018-05-14 15:07:06 -07:00
parent dd004002e9
commit b5fe5f8c8d
5 changed files with 752 additions and 57 deletions

View File

@@ -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.