DATAGEODE-125 - Edit MappingPdxSerializer type filter documentation to cover includes and excludes.

This commit is contained in:
John Blum
2018-08-03 17:53:17 -07:00
parent 821cc2b2a0
commit 77cee2a974
2 changed files with 116 additions and 56 deletions

View File

@@ -5,7 +5,7 @@ This section covers:
* <<mapping.entities>>
* <<mapping.repositories>>
* The <<Mapping PDX Serializer>>
* <<Mapping PDX Serializer>>
[[mapping.entities]]
== Entity Mapping
@@ -125,18 +125,18 @@ You can even wrap the `update` service method in a Spring managed transaction, e
or a global transaction.
[[mapping.pdx-serializer]]
== Mapping PDX Serializer
== MappingPdxSerializer
{sdg-name} provides a custom {x-data-store-javadoc}/org/apache/geode/pdx/PdxSerializer.html[`PdxSerializer`]
implementation that uses the mapping information to customize entity serialization.
implementation, called `MappingPdxSerializer`, that uses Spring Data mapping metadata to customize entity serialization.
It also lets you customize entity instantiation by using the Spring Data `EntityInstantiator` abstraction.
By default, the serializer uses a `ReflectionEntityInstantiator` that uses the persistence constructor of
the mapped entity (the default constructor, a singly declared constructor, or a constructor explicitly
annotated with `@PersistenceConstructor`).
The serializer also lets you customize entity instantiation by using the Spring Data `EntityInstantiator` abstraction.
By default, the serializer use the `ReflectionEntityInstantiator`, which uses the persistence constructor of
the mapped entity. The persistence constructor is either the default constructor, a singly declared constructor,
or a constructor explicitly annotated with `@PersistenceConstructor`.
To provide arguments for constructor parameters, the serializer reads fields with the named constructor parameter,
explicitly specified using Spring's `@Value` annotation, from the supplied
explicitly identified by using Spring's `@Value` annotation, from the supplied
{x-data-store-javadoc}/org/apache/geode/pdx/PdxReader.html[`PdxReader`],
as shown in the following example:
@@ -147,43 +147,43 @@ as shown in the following example:
public class Person {
public Person(@Value("#root.thing") String firstName, @Value("bean") String lastName) {
//
}
}
----
====
An entity class annotated in this way has the `thing` field read from the `PdxReader` and passed as the value
An entity class annotated in this way has the "`thing`" field read from the `PdxReader` and passed as the argument value
for the constructor parameter, `firstname`. The value for `lastName` is a Spring bean with the name "`bean`".
In addition to the custom instantiation logic and strategy provided by `EntityInstantiators`,
the `MappingPdxSerializer` also provides capabilities well beyond {data-store-name}'s own
{x-data-store-javadoc}/org/apache/geode/pdx/ReflectionBasedAutoSerializer.html[`ReflectionBasedAutoSerializer`].
While {data-store-name}'s `ReflectionBasedAutoSerializer` conveniently uses Java reflection to populate entities
and uses regular expressions to identify types that should be handled (serialized and deserialized) by
the `ReflectionBasedAutoSerializer`, it cannot, unlike `MappingPdxSerializer`, perform the following:
While {data-store-name}'s `ReflectionBasedAutoSerializer` conveniently uses Java Reflection to populate entities
and uses regular expressions to identify types that should be handled (serialized and deserialized) by the serializer,
it cannot, unlike `MappingPdxSerializer`, perform the following:
* Register custom `PdxSerializer` objects per entity field and property names and types.
* Register custom `PdxSerializer` objects per entity field or property names and types.
* Conveniently identifies ID properties.
* Automatically handles read-only properties.
* Automatically handles transient properties.
* Allows more robust type filtering in a `null`-safe manner (for example, not limited to
only expressing types using regex).
* Allows more robust type filtering in a `null` and type-safe manner (for example, not limited to
only expressing types with 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 and property names and types.
The `MappingPdxSerializer` gives you the ability to register custom `PdxSerializers` based on an entity's field
or property names and types.
For instance, suppose you have defined an entity type modeling a `User` as follows:
For example, suppose you have defined an entity type modeling a `User` as follows:
[source,java]
----
package example.app.auth.model;
package example.app.security.auth.model;
public class User {
@@ -196,10 +196,10 @@ public class User {
----
While the user's name probably does not require any special logic to serialize the value, serializing the password
might require additional logic to handle the sensitive nature of the field or property.
on the other hand might require additional logic 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
beyond TLS alone, 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, as follows:
.Registering custom `PdxSerializers` by POJO field/property type
@@ -212,12 +212,13 @@ 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` consults the custom `PdxSerializer` to serialize
and deserialize all `Password` objects regardless of the containing object (for example, `User`).
application domain model type, the `MappingPdxSerializer` will then consult the custom `PdxSerializer`
to serialize and deserialize all `Password` objects regardless of the containing object (for example, `User`).
However, suppose you want to customize the serialization of only `Passwords` on `User` objects.
However, suppose you want to customize the serialization of `Passwords` only on `User` objects.
To do so, you can register the custom `PdxSerializer` for the `User` type by specifying the fully qualified name
of the `Class's` field or property, as the following example shows:
@@ -227,12 +228,13 @@ of the `Class's` field or property, as the following example shows:
----
Map<?, PdxSerializer> customPdxSerializers = new HashMap<>();
customPdxSerializers.put("example.app.auth.model.User.password", new SaltedHashPasswordPdxSerializer());
customPdxSerializers.put("example.app.security.auth.model.User.password", new SaltedHashPasswordPdxSerializer());
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
----
====
Notice the use of the fully-qualified field or property name (that is `example.app.auth.model.User.password`)
Notice the use of the fully-qualified field or property name (that is `example.app.security.auth.model.User.password`)
as the custom `PdxSerializer` registration key.
NOTE: You could construct the registration key by using a more logical code snippet, such as the following:
@@ -246,6 +248,8 @@ Like {data-store-name}'s `ReflectionBasedAutoSerializer`, {sdg-acronym}'s `Mappi
determine the identifier of the entity. However, `MappingPdxSerializer` does so by using Spring Data's mapping metadata,
specifically by finding the entity property designated as the identifier using Spring Data's
{spring-data-commons-javadoc}/org/springframework/data/annotation/Id.html[`@Id`] annotation.
Alternatively, any field or property named "`id`", not explicitly annotated with `@Id`, is also designated as
the entity's identifier.
For example:
@@ -270,7 +274,7 @@ when the `PdxSerializer.toData(..)` method is called during serialization.
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 by following the
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans]specification (as Spring does),
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans] specification (as Spring does),
you might define a POJO with a read-only property, as follows:
[source,java]
@@ -289,29 +293,30 @@ class ApplicationDomainType {
}
----
The `readOnly` property is "`read-only`" because it does not provide a setter method. It has only a getter method.
In this case, the `readOnly` property (not to be confused with the `readOnly` `DomainType` field) is considered
"`read-only`".
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 a result, the `MappingPdxSerializer` does not try to write this value back when populating an instance of
`DomainType` in the `PdxSerializer.fromData(:Class<?>, :PdxReader)` method.
As a result, the `MappingPdxSerializer` will not try to set a value for this property when populating an instance of
`ApplicationDomainType` in the `PdxSerializer.fromData(:Class<ApplicationDomainType>, :PdxReader)` method
during deserialization, particularly if a value is present in the PDX serialized bytes.
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 that you can leverage this feature as is appropriate for your application's use cases
and requirements. If you want the field or property to always be written, you can define a setter.
to set 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's use cases
and requirements. If you want the field or property to always be written, simply define a setter method.
[[mapping.pdx-serializer.transient-properties]]
=== Mapping Transient Properties
Likewise, what happens when your entity defines `transient` properties?
You would expect the `transient` fields or properties of your entity not to be serialized to the stream of PDX bytes
when serializing the entity. That is exactly what happens, unlike {data-store-name}'s own `ReflectionBasedAutoSerializer`,
which serializes everything accessible from the object through Java reflection.
You would expect the `transient` fields or properties of your entity not to be serialized to PDX when serializing
the entity. That is exactly what happens, unlike {data-store-name}'s own `ReflectionBasedAutoSerializer`,
which serializes everything accessible from the object through Java Reflection.
The `MappingPdxSerializer` does not serialize any fields or properties that are qualified as being transient either
by using Java's `transient` keyword (in the case of fields) or by using the
The `MappingPdxSerializer` will not serialize any fields or properties that are qualified as being transient, either
by using Java's own `transient` keyword (in the case of class instance fields) or by using the
{spring-data-commons-javadoc}/org/springframework/data/annotation/Transient.html[`@Transient`]
Spring Data annotation on either fields or properties.
@@ -340,21 +345,21 @@ class Process {
}
----
Neither the `Process` `id` field nor the readable `hostname` property are written to the PDX serialized bytes.
Neither the `Process` `id` field nor the readable `hostname` property are written to PDX.
[[mapping.pdx-serializer.type-filtering]]
=== Filtering by Class types
=== Filtering by Class Type
Similar to {data-store-name}'s `ReflectionBasedAutoSerializer`, {sdg-acronym}'s `MappingPdxSerializer` lets you filter
the types of objects that the `MappingPdxSerializer` serializes and deserializes.
the types of objects that are serialized and deserialized.
However, unlike {data-store-name}'s `ReflectionBasedAutoSerializer`, which uses complex regular expressions to express
which types the serializer handles, {sdg-acronym}'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.
If you like to use regular expressions, you can implement a `Predicate` by using
Java's https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html[regular expression support].
TIP: If you like to use regular expressions, you can 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` by using convenient
and appropriate API methods, including:
@@ -362,21 +367,75 @@ https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#and-
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()`].
The following example shows the `Predicate` API in use:
The following example shows the `Predicate` API in action:
[source,java]
----
Predicate<Class<?>> customerTypes =
type -> Customer.class.getPackage().getName().startsWith(type.getName());
type -> Customer.class.getPackage().getName().startsWith(type.getName()); // Include all types in the same package as `Customer`
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
Predicate includedTypes = customerTypes
.or(type -> User.class.isAssignble(type)); // Additionally, include User sub-types (e.g. Admin, Guest, etc)
mappingPdxSerializer.setTypeFilters(typeFilters);
mappingPdxSerializer.setIncludeTypeFilters(includedTypes);
mappingPdxSerializer.setExcludeTypeFilters(
type -> !Reference.class.getPackage(type.getPackage()); // Exclude Reference types
----
NOTE: In addition to setting your own type filtering `Predicates`, SDG's `MappingPdxSerializer` now automatically
registers pre-defined `Predicates` that filter 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.
NOTE: Any `Class` object passed to your `Predicate` is guaranteed not to be `null`.
{sdg-acronym}'s `MappingPdxSerializer` includes support for both include and exclude class type filters.
[[mapping.pdx-serializer.type-filtering.execludes]]
==== Exclude Type Filtering
By default, {sdg-acronym}'s `MappingPdxSerializer` registers pre-defined `Predicates` that filter, or exclude types
from the folliowing packages:
* `java.*`
* `com.gemstone.gemfire.*`
* `org.apache.geode.*`
* `org.springframework.*`
In addition, the `MappingPdxSerializer` filters `null` objects when calling `PdxSerializer.toData(:Object, :PdxWriter)`
and `null` class types when calling `PdxSerializer.fromData(:Class<?>, :PdxReader)` methods.
It is very easy to add exclusions for other class types, or an entire package of types, by simply defining a `Predicate`
and adding it to the `MappingPdxSerializer` as shown earlier.
The `MappingPdxSerializer.setExcludeTypeFilters(:Predicate<Class<?>>)` method is additive, meaning it composes
your application-defined type filters with the existing, pre-defined type filter `Predicates` indicated above
using the `Predicate.and(:Predicate<Class<?>>)` method.
However, what if you want to include a class type (for example, `java.security Principal`) implicitly excluded by
the exclude type filters? See <<mapping.pdx-serializer.type-filtering.includes>>.
[[mapping.pdx-serializer.type-filtering.includes]]
==== Include Type Filtering
If you want to include a class type explicitly, or override a class type filter that implicitly excludes a class type
required by your application (for example, `java.security.Principal`, which is excluded by default with the `java.*`
package exclude type filter on `MappingPdxSerializer`), then just define the appropriate `Predicate` and add it to
the serializer using `MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>)` method, as follows:
[source,java]
----
Predicate<Class<?>> principalTypeFilter =
type -> java.security.Principal.class.isAssignableFrom(type);
mappingPdxSerializer.setIncludeTypeFilters(principalTypeFilters);
----
Again, the `MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>)` method,
like `setExcludeTypeFilters(:Predicate<Class<?>>)`, is additive and therefore composes any passed type filter
using `Predicate.or(:Predicate<Class<?>>)`. This means you may call `setIncludeTypeFilters(:Predicate<Class<?>>)`
as many time as necessary.
When include type filters are present, then the `MappingPdxSerializer` makes a decision of whether to de/serialize
an instance of a class type when the class type is either not implicitly excluded OR when the class type
is explicitly included, whichever returns true. Then, an instance of the class type will be serialized
or deserialized appropriately.
For example, when a type filter of `Predicate<Class<Principal>>` is explicitly registered as shown previously,
it cancels out the implicit exclude type filter on `java.*` package types.

View File

@@ -540,6 +540,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
* @see java.lang.Object
* @see java.lang.Class
*/
@SuppressWarnings("unchecked")
Object doFromData(Class<?> type, PdxReader reader) {
GemfirePersistentEntity<?> entity = getPersistentEntity(type);