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.
|
||||
|
||||
Reference in New Issue
Block a user