Skip to content
Bob Vawter edited this page Jun 29, 2012 · 8 revisions

FlatPack Core Apis

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.

Entities

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

Properties of the following types are supported:

  • All primitives and their boxed counterparts
  • Strings
  • Enums
  • java.util.UUID
  • Other HasUuid entities
  • 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 String or Object one-arg contractor, serialized as its toString() representation, including alljoda-time types
  • com.google.gson.JsonElement or its subtypes

Creating a FlatPack stack

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.

CodexMapper

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().

EntityResolver

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.

RoleMapper and PrincipalMapper

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 @InheritPrincipal are traversed as well.
  • If the set contains the Principal associated with the call to Unpacker.unpack(), the mutation is allowed.
  • Newly-created objects (i.e. ones for which EntityResolver returnd null) 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();
}

TypeSource

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.

Unresolvable types

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.

FlatPackEntity

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.

value

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));

data

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().

errors, warnings

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.

Packer, Unpacker

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();
  }
}

Clone this wiki locally