Annotation Interface PermazenField


@Retention(RUNTIME) @Target({ANNOTATION_TYPE,METHOD}) @Documented public @interface PermazenField
Java annotation for defining simple fields (including reference fields that refer to other Java model object types) and Counter fields.

This annotation is used in two scenarios:

  • To configure a simple or counter database field, by annotating the corresponding abstract Java bean property "getter" method
  • To configure the sub-field of a complex database field (i.e., set, list, or map), that is, a collection element field, or a map key or value field. In this case this annotation nests within the corresponding @PermazenListField, @PermazenSetField, or @PermazenMapField annotation.

When auto-generation of properties is enabled, use of this annotation is not required unless you want to override the defaults; see PermazenType.autogenFields().

The annotated method's declaring class does not have to be a @PermazenType-annotated type; annotations are "inherited" and so apply to all @PermazenType-annotated sub-types.

Reference Fields

If the type of the field is a @PermazenType-annotated Java model object type, or any supertype thereof, then the field is a reference field. Reference fields are simple fields that refer to other database objects.

Non-Reference Fields

If the field is not a reference field, the field's Java type is inferred from the type of the annotated method or, in the case of complex sub-fields, the generic type of the collection class. The field's Java type must be supported by some Encoding registered in the EncodingRegistry that is configured for the database so that the field's values can be encoded into byte[] array values in the key/value store.

See DefaultEncodingRegistry for a list of built-in (pre-defined) encodings.

To use a user-defined encoding, configure a custom EncodingRegistry that knows about the encoding and then refer to it by its unique EncodingId via encoding().

Referential Integrity

In general, reference fields may reference objects that don't actually exist. This can happen in one of two ways: (a) a field is set to an invalid reference, or (b) a field references a valid object that is subsequently deleted. The allowDeleted() and inverseDelete() properties, respectively, control whether (a) or (b) is permitted.

By default, neither (a) nor (b) is allowed; if either is attempted, a DeletedObjectException is thrown. This ensures references are always valid.

Indexing

Simple fields may be indexed by marking them as indexed(); see PermazenTransaction for information on querying indexes. This includes reference fields and fields with user-defined custom encodings.

Reference fields are always indexed.

Counter fields may not be indexed.

Two or more simple fields may be indexed together in a composite index; see @PermazenCompositeIndex.

Reference Cascades

Reference cascades allow you to define an arbitrary graph of objects by specifying the reference fields that constitute the edges of the graph. Reference cascades are identified by name, and a reference field is included in a reference cascade when that name appears in forwardCascades() and/or inverseCascades(). Reference fields in reference cascades can be traversed in either the forward or reverse directions.

For example, the PermazenObject methods copyIn(), copyOut(), and copyTo() copy a graph of related objects between transactions by first copying a starting object and then cascading through matching reference fields, repeating recursively. There is also PermazenTransaction.cascade() which performs a general purpose cascade exploration and returns the corresponding set of objects.

Which reference fields are traversed in a particular find or copy operation is determined by the supplied cascade name. Outgoing references are traversed if the cascade name is in the reference field's forwardCascades() property, while incoming references from other objects are traversed (in the reverse direction) if the cascade name is in the referring object's reference field's inverseCascades().

For example:


  @PermazenType
  public interface TreeNode extends PermazenObject {

      /**
       * Get the parent of this node, or null if node is a root.
       */
      @PermazenField(forwardCascades = { "tree", "ancestors" }, inverseCascades = { "tree", "descendants" })
      TreeNode getParent();
      void setParent(TreeNode parent);

      /**
       * Get the children of this node.
       */
      @ReferencePath("<-TreeNode.parent")
      NavigableSet<TreeNode> getChildren();

      default TreeNode copySubtreeTo(PermazenTransaction dest) {
          return (TreeNode)this.cascadeCopyTo(dest, "descendants", false);
      }

      default TreeNode copyWithAnscestorsTo(PermazenTransaction dest) {
          return (TreeNode)this.cascadeCopyTo(dest, "ancestors", false);
      }

      default TreeNode copyEntireTreeTo(PermazenTransaction dest) {
          return (TreeNode)this.cascadeCopyTo(dest, "tree", false);
      }

      default TreeNode cloneEntireTreeTo(PermazenTransaction dest) {
          return (TreeNode)this.cascadeCopyTo(dest, "tree", true);
      }

      default TreeNode cloneEntireTree() {
          return (TreeNode)this.cascadeCopyTo(this.getTransaction(), "tree", true);
      }
  }
 

References and Deletion

Reference fields have configurable behavior when the referring object or the referred-to object is deleted; see forwardDelete() and inverseDelete().

Uniqueness Constraints

Simple fields that are not complex sub-fields may be marked as unique() to impose a uniqueness constraint on the field. Uniqueness constraints function as an implicit validation constraint. In other words, the uniqueness constraint is verified when the validation queue is processed, and is affected by the transaction's configured ValidationMode.

A uniqueness constraint applies to all objects that are instances of the class in which the field is declared. For example, if classes Dog and Cat both implement Pet, then a uniqueness constraint on a field declared in Pet would apply across all dogs and cats, whereas unique constraints on fields declared in Dog and/or Cat would only apply to that specific animal. Note this remains true even when Dog and Cat declare the same field; for example, if Dog and Cat both declare a "name" field, then you could have two pets with the same name, but only if one is a Dog and one is a Cat.

Optionally, specific values may be marked as excluded from the uniqueness constraint via uniqueExcludes(). If so, the specified values may appear in more than one object without violating the constraint.

In ValidationMode.AUTOMATIC, any upgraded PermazenObjects are automatically added to the validation queue, so a uniqueness constraint added in a new schema will be automatically verified when any object is upgraded.

Beware however, that like most other types of validation constraint, adding or changing a uniqueness constraint can cause valid database objects to become invalid without immediate notice, or at least not until if/when the object is revalidated in some future transaction. In such a situation, you can force a schema change - and therefore revalidation on the next access - by incrementing PermazenType.schemaEpoch() in the field's containing type.

Upgrade Conversions

When a field's type has changed in a new schema, the old field value can be automatically converted into the new type. See upgradeConversion() for how to control this behavior.

Meta-Annotations

This annotation may be configured indirectly as a Spring meta-annotation when spring-core is on the classpath.

  • Element Details

    • name

      String name
      The name of this field.

      If equal to the empty string (default value), the name is inferred from the name of the annotated Java bean getter method.

      For sub-fields of complex fields, this property must be left unset.

      Returns:
      the name of the field
      Default:
      ""
    • encoding

      String encoding
      Specify the encoding for this field by EncodingId URN.

      If set, this must equal the EncodingId of an Encoding registered in the EncodingRegistry associated with the Database instance, and the annotated method's return type must match the Encoding's supported Java type.

      If this is left unset (empty string), then the Java type is inferred from the return type of the getter method and the Encoding is found via EncodingRegistry.getEncoding(TypeToken).

      For any of Permazen's built-in types, the Permazen URN prefix "urn:fdc:permazen.io:2020:" may be omitted. Otherwise, see EncodingId for the required format. Custom encodings can be found automatically on the application class path; see DefaultEncodingRegistry for details.

      For reference fields, this property must be left unset.

      For sub-fields of complex fields, this property can be used to force a primitive type instead of a primitive wrapper type. In that case, the complex field will disallow null values. For example:

        @PermazenSetField(element = @PermazenField(type = "float")) // nulls will be disallowed
        public abstract List<Float> getScores();
       
      Returns:
      URN identifying the field's encoding
      See Also:
      Default:
      ""
    • storageId

      int storageId
      Storage ID for this field.

      Normally this value is left as zero, in which case a value will be automatically assigned.

      Otherwise, the value should be positive and unique within the contained class.

      Returns:
      the field's storage ID, or zero for automatic assignment
      Default:
      0
    • indexed

      boolean indexed
      Whether this field is indexed or not.

      Setting this property to true creates a simple index on this field. To have this field participate in a composite index on multiple fields, use @PermazenCompositeIndex.

      Note: reference fields are always indexed (for reference fields, this property is ignored).

      Returns:
      whether the field is indexed
      See Also:
      Default:
      false
    • forwardCascades

      String[] forwardCascades
      Define forward reference cascades for the annotated reference field.

      When following a cascade of references, if the cascade name is one of the names listed here, and an object with the annotated reference field is encountered, then the annotated reference field will will be traversed in the forward direction.

      Cascade names must be non-empty.

      For non-reference fields this property must be equal to its default value.

      Returns:
      forward cascade names for the annotated reference field
      See Also:
      Default:
      {}
    • inverseCascades

      String[] inverseCascades
      Define inverse find/copy cascades for the annotated reference field.

      When following a cascade of references, if the cascade name is one of the names listed here, and an object is encountered that is referred to through the annotated reference field, then the annotated reference field will will be traversed in the inverse direction.

      Cascade names must be non-empty.

      For non-reference fields this property must be equal to its default value.

      Returns:
      inverse cascade names for the annotated reference field
      See Also:
      Default:
      {}
    • inverseDelete

      DeleteAction inverseDelete
      For reference fields, configure the behavior when the referred-to object is deleted.

      For non-reference fields this property must be equal to its default value.

      Returns:
      desired behavior when a referenced object is deleted
      See Also:
      Default:
      EXCEPTION
    • forwardDelete

      boolean forwardDelete
      For reference fields, configure cascading behavior when the referring object is deleted. If set to true, the referred-to object is automatically deleted as well.

      For non-reference fields this property must be equal to its default value.

      Returns:
      whether deletion should automatically propagate to the referred-to object
      See Also:
      Default:
      false
    • unique

      boolean unique
      Require this field's value to be unique among all database objects.

      This property creates an implicit uniqueness validation constraint.

      The constraint will be checked any time normal validation is performed on an object containing the field. More precisely, a uniqueness constraint behaves like a JSR 303 validation constraint with groups() = { Default.class, UniquenessConstraints.class }. Therefore, uniqueness constraints are included in default validation, but you can also validate only uniqueness constraints via myobj.revalidate(UniquenessConstraints.class).

      This property must be false for sub-fields of complex fields, and for any field that is not indexed.

      For reference fields, a uniqueness constraint enforces a one-to-one relationship.

      Returns:
      whether the field's value should be unique
      See Also:
      Default:
      false
    • uniqueExcludes

      Values uniqueExcludes
      Specify field value(s) that should be excluded from the uniqueness constraint.

      Examples:

        // Exclude objects with null names from the uniqueness constraint
        @PermazenField(indexed = true, unique = true, uniqueExcludes = @Values(nulls = true))
        public abstract String getName();
      
        // Exclude objects with non-finite priorities from the uniqueness constraint
        @PermazenField(indexed = true, unique = true, uniqueExcludes = @Values({ "Infinity", "-Infinity", "NaN" }))
        public abstract float getPriority();
       

      Use of @Values(nonNulls = true) would be somewhat unusual; that would mean there can be at most one object with a null value in the field, but otherwise there are no restrictions. Specifying both nulls() and nonNulls() generates an error, as that would exclude every object from the constraint, rendering it pointless.

      This property must be left empty when unique() is false.

      Returns:
      field values to be excluded from the uniqueness constraint
      See Also:
      Default:
      @io.permazen.annotation.Values
    • allowDeleted

      boolean allowDeleted
      Allow the field to reference non-existent objects in normal transactions.

      For non-reference fields, this property must be equal to its default value.

      Otherwise, if this property is set to false, the field is disallowed from ever referring to a non-existent object; instead, a DeletedObjectException will be thrown. When used together with DeleteAction.EXCEPTION (see inverseDelete()), the field is guaranteed to never be a dangling reference.

      This property only applies to regular (non-detached) transactions.

      For consistency, this property must be set to true when inverseDelete() is set to DeleteAction.IGNORE.

      Returns:
      whether the reference field should allow assignment to deleted objects in normal transactions
      See Also:
      Default:
      false
    • upgradeConversion

      UpgradeConversionPolicy upgradeConversion
      Specify the UpgradeConversionPolicy policy to apply when this field's type has changed due to a schema change.

      Permazen supports schema changes that alter a field's type, and in some cases can automatically convert field values from the old to the new type (for example, from the int value 1234 to the String value "1234").

      See Encoding.convert() for details about conversions between field encodings. In addition, Counter fields may be automatically converted to/from any numeric Java primitive (or primitive wrapper) type.

      This property defines the UpgradeConversionPolicy for the annotated field when upgrading an object from some other schema to the current schema.

      Automatic upgrade conversion is only supported for plain simple fields. For sub-fields of complex fields, this property is ignored.

      Note that arbitrary conversion logic is always possible using @OnSchemaChange methods.

      Returns:
      upgrade conversion policy for this field
      See Also:
      Default:
      ATTEMPT