-
Notifications
You must be signed in to change notification settings - Fork 2
Core
This document describes the various functional parts of the FlatPack stack. Unless otherwise noted, all types in this document are provided by the flatpack-core Maven artifact.
An entity is a bean-like object (having getters and setters) that implements the HasUuid interface. An entity's UUID is distinct from the its primary database key. By using UUIDs to identify entities instead of database keys, it avoids the problem of having to juggle ephemeral and persistent ids for entities created by a client before they are persisted by the server.
The utility type BaseHasUuid provides a default implementation of HasUuid. Its defaultUuid() method may be overridden for value-like entities (see EntityDescription).
Properties of the following types are supported:
- All primitives and their boxed counterparts
- Strings
- Enums
java.util.UUID- Other
HasUuidentities - Lists, Sets, and arrays of any supported type
- Maps with keys of strings or entities and values of any supported type
- Any value type with a
StringorObjectone-arg contractor, serialized as itstoString()representation, including alljoda-timetypes -
com.google.gson.JsonElementor its subtypes
An instance of FlatPack is constructed by creating a Configuration object and calling FlatPack.create().
Configuration configuration = new Configuration()
.addTypeSource(new SearchTypeSource("com.example.domain"))
.withIgnoreUnresolvableTypes(true);
FlatPack flatpack = FlatPack.create(configuration);Instances of all FlatPack public APIs are immutable and thread-safe.
The FlatPack type provides access to three service objects: Packer, Unpacker, and TypeContext. The first two types are used to serialize and deserialize FlatPackEntity instances, which are an intermediate representation of the payload. The TypeContext may be used to inspect the FlatPack stack's type-system. The ApiDescriber class in flatpack-jersey makes use of this functionality to generate an ApiDescription which is used by flatpack-fast to generate client access libraries.
A CodexMapper provides the stack with type-specific strategies to serialize and deserialize values. The [Codex] instances must be immutable since only one Codex per java.lang.Type will be requested. The DefaultCodexMapper is always installed, but it will be overridden by anything passed to addCodexMapper().
An EntityResolver is optional can be provided to allow Unpacker to apply incoming property values to existing entities or to control how new entity instances will be instantiated. Users that wish to obtain entity instances from a dependency-injection framework will delegate to the framework's injector.
The RoleMapper and PrincipalMapper are optional and used in conjunction with one another to restrict property access based on role annotations and to prevent an unauthorized java.security.Principal from mutating entities that the principal should not have access to.
The RoleMapper maps a string role name onto a Class literal, usually an interface. The role-type(s) of the current Principal (provided by PrincipalMapper) must be assignable to one or more of the role-type(s) defined on a getter or setter in order for that principal to be allowed to read or write the property.
During deserialization, any entities provided by an EntityResolver are subject to principal-based security checks to prevent payloads from mutating arbitrary objects data allows arbitrary uuids to be specified. In order for an entity to be considered mutable, the following must happen:
-
isAccessEnforced()is called to determine if access-control should be applied for the entity or user. - A set of allowed principals is computed by calling
getPrincipals()for the resolved object. Any properties annotated with@InheritPrincipalare traversed as well. - If the set contains the
Principalassociated with the call toUnpacker.unpack(), the mutation is allowed. - Newly-created objects (i.e. ones for which
EntityResolverreturndnull) are never subject to this access control, since no property values exist to compute the allowed-principals set.
class Employee {
@RolesAllowed({Role.BOSS, Role.EMPLOYEE})
public String getName() {}
public void setName(String string) {}
// If the PrincipalMapper maps the associated Boss to the current Principal
// then the Employee instance can be edited.
@InheritPrincipal
public Boss getBoss();
}In order to support simple payload type names and polymorphism, it is advantageous to know all serializable types when the FlatPack stack is created. One or more TypeSource instances must be provided to the Configuration object. The SearchTypeSource (available from the flatpack-search Maven artifact) will scan the class path roots for types that extend HasUuid. Generated client libraries will contain a static implementation of TypeSource.
By default, it is an error for a data section to contain a type name that does not resolve to a known entity type. While this behavior is usually desirable for server code (since it tends to indicate misbehaving clients), it would make clients unacceptably brittle. The withIgnoreUnresolvableTypes() method may be used to ignored types in payloads that have no mapping.
A FlatPackEntity is a description of a serialized payload. It has property accessors that correspond to the structures in the wire format and also allows control over how a specific payload should be serialized.
Each payload has a single root value property, which is accessed via get / withValue(). The FlatPackEntity type is parameterized based on the type of the value that the payload is expected to contain.
Due to the lack of generic type literals in the Java language, FlatPackEntity implements a type-reference pattern to tell Unpacker how to interpret the top-level value in a payload. If one of the predefined factory methods doesn't match the kind of data that you have, an anonymous subtype must be constructed with the desired parameterization.
// A sample of some of the convenience factory methods
FlatPackEntity.entity(someMerchantLocation);
FlatPackEntity.collectionOf(MerchantLocation.class).withValue(someList);
FlatPackEntity.stringMapOf(MerchantLocation.class).withValue(someMapOfStringToLocation);
// Constructing a FlatPackEntity with arbitrary parameterization
new FlatPackEntity<List<UUID>>(){}.withValue(Arrays.asList(uuid1, uuid2));During serialization, the root value object is recursively scanned for references to other entities. Every entity encountered is added once to the data section of the payload. The TraversalMode may be set to one of three values: SIMPLE, SPARSE, or DEEP. The default, SIMPLE, skips any property determined to be deep-traversal-only (see Annotations, to avoid excessively large payloads. The DEEP mode enables traversal of these kinds of properties. The SPARSE mode disables scanning entirely.
Entities that are not reachable from the root value (e.g. out-of-band data or when SPARSE mode is enabled) can be added to the data section with the the addExtraEntity() methods.
Entities that implements HasTimestamps can be elided from a serialized payload by calling withLastModifiedTime().
Users using role-based property access will provide a java.security.Principal to the Packer via withPrincipal().
The errors and warnings segments are set with addError() and addWarning. A convenience method addConstraintViolations() will map a set of ConstraintViolation objects in the errors segment.
The Packer type writes a FlatPackEntity into a Writer. The Unpacker type performs the inverse.
public class Demo {
@Inject
FlatPack flatpack;
public MerchantLocation photocopy(MerchantLocation location) throws IOException {
// Buffer
StringWriter out = new StringWriter();
// Write the location into the buffer
flatpack.getPacker().pack(FlatPackEntity.entity(location), out);
// Reconstitute the data
FlatPackEntity<MerchantLocation> unpacked = flatpack.getUnpacker()
.unpack(MerchantLocation.class, new StringReader(out.toString()), null);
return unpacked.getValue();
}
}