All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.annotation.PermazenField Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.annotation;

import com.google.common.reflect.TypeToken;

import io.permazen.Counter;
import io.permazen.PermazenObject;
import io.permazen.PermazenTransaction;
import io.permazen.UniquenessConstraints;
import io.permazen.UpgradeConversionPolicy;
import io.permazen.ValidationMode;
import io.permazen.core.Database;
import io.permazen.core.DeleteAction;
import io.permazen.core.DeletedObjectException;
import io.permazen.encoding.DefaultEncodingRegistry;
import io.permazen.encoding.Encoding;
import io.permazen.encoding.EncodingId;
import io.permazen.encoding.EncodingRegistry;

import jakarta.validation.groups.Default;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Java annotation for defining simple fields (including reference fields that refer to other Java model object types)
 * and {@link 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 {@code element} field, or a map {@code key} or {@code value} field. In this case this annotation * nests within the corresponding {@link PermazenListField @PermazenListField}, * {@link PermazenSetField @PermazenSetField}, or {@link PermazenMapField @PermazenMapField} annotation. *
* *

* When auto-generation of properties is enabled, use of this annotation is not required unless you want to override * the defaults; see {@link PermazenType#autogenFields}. * *

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

Reference Fields

* *

* If the type of the field is a {@link PermazenType @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 {@link Encoding} registered in the {@link EncodingRegistry} that is * {@linkplain io.permazen.PermazenConfig.Builder configured} for the database so that the field's values can be encoded * into {@code byte[]} array values in the key/value store. * *

* See {@link DefaultEncodingRegistry} for a list of built-in (pre-defined) encodings. * *

* To use a user-defined encoding, configure a custom {@link EncodingRegistry} that knows about the encoding and * then refer to it by its unique {@link EncodingId} via {@link #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 {@link #allowDeleted} and {@link #inverseDelete} properties, respectively, control whether (a) or (b) is permitted. * *

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

Indexing

* *

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

* Reference fields are always indexed. * *

* {@link Counter} fields may not be indexed. * *

* Two or more simple fields may be indexed together in a composite index; see * {@link PermazenCompositeIndex @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 {@link #forwardCascades} and/or {@link #inverseCascades}. Reference fields in reference cascades * can be traversed in either the forward or reverse directions. * *

* For example, the {@link PermazenObject} methods {@link PermazenObject#copyIn copyIn()}, * {@link PermazenObject#copyOut copyOut()}, and {@link PermazenObject#copyTo 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 {@link PermazenTransaction#cascade 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 {@link #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 {@link #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 {@link #forwardDelete} and {@link #inverseDelete}. * *

Uniqueness Constraints

* *

* Simple fields that are not complex sub-fields may be marked as {@link #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 {@link ValidationMode}. * *

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

* Optionally, specific values may be marked as excluded from the uniqueness constraint via {@link #uniqueExcludes}. * If so, the specified values may appear in more than one object without violating the constraint. * *

* In {@link ValidationMode#AUTOMATIC}, any upgraded {@link PermazenObject}s 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 {@link 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 {@link #upgradeConversion} for how to control this behavior. * *

Meta-Annotations

* *

* This annotation may be configured indirectly as a Spring * meta-annotation * when {@code spring-core} is on the classpath. */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Documented public @interface PermazenField { /** * 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. * * @return the name of the field */ String name() default ""; /** * Specify the encoding for this field by {@link EncodingId} URN. * *

* If set, this must equal the {@link EncodingId} of an {@link Encoding} registered in the {@link EncodingRegistry} * associated with the {@link Database} instance, and the annotated method's return type must match the * {@link Encoding}'s {@linkplain Encoding#getTypeToken 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 {@link Encoding} is found via {@link EncodingRegistry#getEncoding(TypeToken)}. * *

* For any of Permazen's built-in types, the Permazen URN prefix {@value io.permazen.encoding.EncodingIds#PERMAZEN_PREFIX} * may be omitted. Otherwise, see {@link EncodingId} for the required format. Custom encodings can be found automatically * on the application class path; see {@link 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();
     * 
* * @return URN identifying the field's encoding * @see Encoding * @see EncodingRegistry#getEncoding(EncodingId) */ String encoding() default ""; /** * 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. * * @return the field's storage ID, or zero for automatic assignment */ int storageId() default 0; /** * 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 {@link PermazenCompositeIndex @PermazenCompositeIndex}. * *

* Note: reference fields are always indexed (for reference fields, this property is ignored). * * @return whether the field is indexed * @see PermazenCompositeIndex @PermazenCompositeIndex */ boolean indexed() default false; /** * 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. * * @return forward cascade names for the annotated reference field * @see io.permazen.PermazenObject#copyTo PermazenObject.copyTo() * @see io.permazen.PermazenTransaction#cascade PermazenTransaction.cascade() */ String[] forwardCascades() default {}; /** * 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. * * @return inverse cascade names for the annotated reference field * @see io.permazen.PermazenObject#copyTo PermazenObject.copyTo() * @see io.permazen.PermazenTransaction#cascade PermazenTransaction.cascade() */ String[] inverseCascades() default {}; /** * For reference fields, configure the behavior when the referred-to object is * {@linkplain PermazenObject#delete deleted}. * *

* For non-reference fields this property must be equal to its default value. * * @return desired behavior when a referenced object is deleted * @see #forwardDelete * @see io.permazen.PermazenObject#delete */ DeleteAction inverseDelete() default DeleteAction.EXCEPTION; /** * For reference fields, configure cascading behavior when the referring object is * {@linkplain PermazenObject#delete 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. * * @return whether deletion should automatically propagate to the referred-to object * @see #inverseDelete * @see PermazenObject#delete */ boolean forwardDelete() default false; /** * 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 {@code groups() = }{ {@link Default}{@code .class, * }{@link UniquenessConstraints}{@code .class} }. Therefore, uniqueness constraints * are included in default validation, but you can also validate only uniqueness constraints via * {@link PermazenObject#revalidate 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. * * @return whether the field's value should be unique * @see #uniqueExcludes * @see io.permazen.UniquenessConstraints */ boolean unique() default false; /** * 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 {@link Values @Values}{@code (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 * {@link Values#nulls nulls()} and {@link Values#nonNulls nonNulls()} generates an error, as that would exclude * every object from the constraint, rendering it pointless. * *

* This property must be left empty when {@link #unique} is false. * * @return field values to be excluded from the uniqueness constraint * @see #unique */ Values uniqueExcludes() default @Values; /** * 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 {@link DeletedObjectException} will be thrown. When used together with * {@link DeleteAction#EXCEPTION} (see {@link #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 {@link #inverseDelete} is set to {@link DeleteAction#IGNORE}. * * @return whether the reference field should allow assignment to deleted objects in normal transactions * @see #inverseDelete * @see PermazenType#autogenAllowDeleted */ boolean allowDeleted() default false; /** * Specify the {@link 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 {@code int} value {@code 1234} to the {@link String} value * {@code "1234"}). * *

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

* This property defines the {@link 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 {@link OnSchemaChange @OnSchemaChange} methods. * * @return upgrade conversion policy for this field * @see UpgradeConversionPolicy * @see io.permazen.encoding.Encoding#convert Encoding.convert() * @see OnSchemaChange */ UpgradeConversionPolicy upgradeConversion() default UpgradeConversionPolicy.ATTEMPT; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy