Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove requirement that JD providers init user-written metamodel #567

Merged
merged 2 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 28 additions & 34 deletions api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,20 @@
import jakarta.data.Sort;

/**
* <p>Annotates a class to serve as a static metamodel for an entity,
* enabling type-safe access to entity attribute names and related objects,
* such as {@link Sort}s for an attribute.</p>
* <p>Annotates a class which serves as a static metamodel for an entity, enabling
* type-safe access to entity attribute names and related objects such as instances
* of {@link Sort}s for an attribute. A metamodel class contains one or more
* {@code public static} fields corresponding to persistent fields of the entity class.
* The type of each of these fields must be either {@link String}, {@link Attribute},
* or a subinterface of {@code Attribute} defined in this package.</p>
*
* <p>Jakarta Data defines the following conventions for static metamodel classes:</p>
* <ul>
* <li>The name of the static metamodel class should consist of underscore ({@code _})
* followed by the entity class name.</li>
* <li>Fields of type {@code String} should be named with all upper case.</li>
* <li>Fields of type {@code Attribute} should be named in lower case or mixed case.</li>
* </ul>
*
* <p>For example, for the following entity,</p>
*
Expand Down Expand Up @@ -63,11 +74,11 @@
* public static final String NAME_LAST = "name.last";
* public static final String YEAROFBIRTH = "yearOfBirth";
*
* public static volatile {@code SortableAttribute<Person>} ssn; // ssn or id
* public static volatile {@code Attribute<Person>} name;
* public static volatile {@code TextAttribute<Person>} name_first;
* public static volatile {@code TextAttribute<Person>} name_last;
* public static volatile {@code SortableAttribute<Person>} yearOfBirth;
* public static final {@code SortableAttribute<Person>} ssn = new SortableAttributeRecord&lt;&gt;("ssn"); // ssn or id
gavinking marked this conversation as resolved.
Show resolved Hide resolved
* public static final {@code Attribute<Person>} name = new AttributeRecord&lt;&gt;("name");
* public static final {@code TextAttribute<Person>} name_first = new TextAttributeRecord&lt;&gt;("name.first");
* public static final {@code TextAttribute<Person>} name_last = new TextAttributeRecord&lt;&gt;("name.last");
* public static final {@code SortableAttribute<Person>} yearOfBirth = new SortableAttributeRecord&lt;&gt;("yearOfBirth");
* }
* </pre>
*
Expand All @@ -82,34 +93,17 @@
* .size(20);
* </pre>
*
* <p>When a class is annotated with {@code StaticMetamodel} and the
* {@link jakarta.annotation.Generated} annotation is not present, Jakarta Data providers
* that provide a repository for the entity type must assign the value of each field
* that meets the following criteria:</p>
*
* <ul>
* <li>The field is {@code public}, {@code static}, and not {@code final}.</li>
* <li>The field type is {@link String}, {@link Attribute}, or an {@code Attribute} subclass
* from the {@link jakarta.data.metamodel} package.</li>
* <li>The name of the field, ignoring case, matches the name of an entity attribute,
* where the {@code _} character delimits the attribute names of hierarchical structures
* such as embedded classes.</li>
* <li>The value of the field is uninitialized or {@code null}.</li>
* </ul>
* <p>Alternatively, an annotation processor might generate static metamodel classes
* for entities at compile time. The generated classes must be annotated with the
* {@link jakarta.annotation.Generated @Generated} annotation. The fields may be
* statically initialized, or they may be initialized by the provider during system
* initialization. In the first case, the fields are declared {@code final}. In the
* second case, the fields are declared non-{@code final} and {@code volatile}.</p>
Comment on lines +96 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the point of this pull was to remove having providers initialize the static metamodel, but the middle of this paragraph creates the expectation that they will still do that.

Suggested change
* <p>Alternatively, an annotation processor might generate static metamodel classes
* for entities at compile time. The generated classes must be annotated with the
* {@link jakarta.annotation.Generated @Generated} annotation. The fields may be
* statically initialized, or they may be initialized by the provider during system
* initialization. In the first case, the fields are declared {@code final}. In the
* second case, the fields are declared non-{@code final} and {@code volatile}.</p>
* <p>Alternatively, an annotation processor might generate static metamodel classes
* for entities at compile time. The generated classes must be annotated with the
* {@link jakarta.annotation.Generated @Generated} annotation. The fields must be
* statically initialized and must be declared {@code final}.</p>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is dealing with provider-generated models, not user-written models. The idea of the pull is to relax the requirement that the provider initialize user-written models.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we were offering the deferred initialization pattern to provider-generated models prior to this pull. I suppose we never explicitly said they could not do it that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed we allowed it as an option I guess partly because that's how it works in JPA.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I don't see a reason to disallow it.)

*
* <p>In cases where multiple Jakarta Data providers provide repositories for the same
Comment on lines 102 to 103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer applies

Suggested change
*
* <p>In cases where multiple Jakarta Data providers provide repositories for the same

* entity type, no guarantees are made of the order in which the Jakarta Data providers attempt
* to initialize the fields of the class that is annotated with {@code StaticMetamodel}.
* It is recommended to include the {@code volatile} modifier on metamodel fields in case the
* initialization attempt overlaps between multiple providers.</p>
*
* <p>A mixture of {@code final} and non-{@code final} fields is permitted, in which case
* the latter are initialized by the Jakarta Data provider and the former are ignored by it.</p>
*
* <p>Alternatively, an annotation processor might generate fully-implemented static metamodel
* classes for entities at compile time. The generated classes must be annotated with the
* {@link jakarta.annotation.Generated} annotation, which instructs the Jakarta Data provider
* to avoid attempting to initialize any fields in the class at runtime.</p>
* entity type, no guarantees are made of the order in which the Jakarta Data providers
* attempt to initialize the fields of the static metamodel class for that entity.</p>
*
Comment on lines +104 to +106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer applies because Jakarta Data providers don't initialize the static metamodel anymore

Suggested change
* entity type, no guarantees are made of the order in which the Jakarta Data providers
* attempt to initialize the fields of the static metamodel class for that entity.</p>
*

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand Down
46 changes: 23 additions & 23 deletions spec/src/main/asciidoc/repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -582,45 +582,45 @@ List<Order> findByAddress_zipcode(int zipCode);

Jakarta Data provides a static metamodel that allows entity attributes to be accessed by applications in a type-safe manner.

For each entity class, the application developer or a compile-time annotation processor can define a corresponding metamodel class following a prescribed set of conventions. The metamodel class must be annotated with `@StaticMetamodel`, specifying the entity class as its `value`. The metamodel class can contain one or more fields of type `java.lang.String` or `jakarta.data.metamodel.Attribute` (or `Attribute` subclasses from the `jakarta.data.metamodel` package) with modifiers `public` and `static`, but not `final`, with each field named after an entity attribute. The value of each matching field can be left uninitialized (`null`) or can be preinitialized. Generated metamodel classes for which all fields are initialized must be annotated with the `jakarta.annotation.Generated` annotation. Otherwise, a Jakarta Data provider that provides a repository for the entity class initializes each uninitialized `String` and `Attribute` field (as well as fields for `Attribute` subclasses from the `jakarta.data.metamodel` package) for which the field name corresponds to an entity attribute name.
For each entity class, the application developer or a compile-time annotation processor can define a corresponding metamodel class following a prescribed set of conventions.

===== Application Requirements for a Metamodel Class

For each entity class for which the application wishes to request the metamodel,
- The metamodel class must be annotated with `@StaticMetamodel`, specifying the entity class as its `value`.
- The metamodel class contains one or more `public static` fields corresponding to persistent fields of the entity class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The metamodel class contains one or more `public static` fields corresponding to persistent fields of the entity class.
- The metamodel class contains one or more `public static final` fields corresponding to persistent fields of the entity class.

- The type of each of these fields must be either `java.lang.String`, `jakarta.data.metamodel.Attribute`, or a subinterface of `Attribute` from the package `jakarta.data.metamodel`.

- The application defines a class (the metamodel class) and annotates it with the `@StaticMetamodel` annotation.
- The application specifies the `value` of the `@StaticMetamodel` annotation to be an entity class that the application uses in a repository as the result type of a find method or the parameter type of an insert, update, save, or delete method.
The application can use the field values of the metamodel class to obtain artifacts relating to the entity attribute in a type-safe manner, for example, `_Book.title.asc()` or `Sort.asc(_Book.title.name())` or `Sort.asc(_Book.TITLE)` rather than `Sort.asc("title")`.

For each field of the metamodel class that is to be initialized by a Jakarta Data provider,
===== Application Requirements for a Metamodel Class

- The field type must be `java.lang.String`, `jakarta.data.model.Attribute` or an `Attribute` subclass from the `jakarta.data.metamodel` package.
- The field must have the `public` and `static` modifiers, but not the `final` modifier.
- The name of the field, ignoring case, must match the name of an entity attribute, with the `_` character in the field name delimiting the attribute names of hierarchical structures or relationships, such as embedded classes.
- The value of the field must be uninitialized or `null`.
When an application programmer writes a static metamodel class for an entity by hand:

The application is not required to include fields for all entity attributes.
- each field corresponding to a persistent field of an entity must have modifiers `public`, `static`, and `final`, and
- the fields must be statically initialized.

The application can use the field values of the metamodel class to obtain artifacts relating to the entity attribute in a type-safe manner, for example, `_Book.title.asc()` or `Sort.asc(_Book.title.name())` or `Sort.asc(_Book.TITLE)` rather than `Sort.asc("title")`.
The static metamodel class is not required to include a field for every persistent field of the entity.

If the application defines repositories for the same entity class across multiple Jakarta Data providers, no guarantee is made of the order in which the fields of the metamodel class are assigned by the Jakarta Data providers.
A convenience implementation of each subinterface of `Attribute` is provided in the package `jakarta.data.metamodel.impl`.

===== Compile-time Annotation Processor Requirements for a Metamodel Class

A compile-time annotation processor that generates a metamodel class must follow the same requirements as stated for the Application under "Application Requirements for a Metamodel Class". If all fields of the metamodel class are preinitialized, then the metamodel class must be annotated with `jakarta.annotation.Generated`. This signals the Jakarta Data providers to avoid attempting to initialize any fields of the class.
When an annotation processor generates a static metamodel class for an entity:

===== Jakarta Data Provider Requirements for a Metamodel Class
- the metamodel class must be annotated with `jakarta.annotation.Generated`,
- each field corresponding to a persistent field of an entity must have modifiers `public`, `static`, and either `final` or `volatile`,
- the name of each field, ignoring case, must match the name of an entity attribute, according to the conventions specified below in <<Conventions for Metamodel Fields>>, and with the `_` character in the field name delimiting the attribute names of hierarchical structures or relationships, such as embedded classes.
Comment on lines +609 to +610
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- each field corresponding to a persistent field of an entity must have modifiers `public`, `static`, and either `final` or `volatile`,
- the name of each field, ignoring case, must match the name of an entity attribute, according to the conventions specified below in <<Conventions for Metamodel Fields>>, and with the `_` character in the field name delimiting the attribute names of hierarchical structures or relationships, such as embedded classes.
- each field corresponding to a persistent field of an entity must have modifiers `public`, `static`, and `final`,
- the name of each field, ignoring case, must match the name of an entity attribute, according to the conventions specified below in <<Conventions for Metamodel Fields>>, and with the `_` character in the field name delimiting the attribute names of hierarchical structures or relationships, such as embedded classes.


The Jakarta Data provider observes classes that are annotated with the `@StaticMetamodel` annotation where the `jakarta.annotation.Generated` is not also present. If the `value` of the `@StaticMetamodel` annotation is an entity class for which the Jakarta Data provides a repository implementation, then the Jakarta Data provider must initialize the value of each uninitialized (`null` valued) field that meets the criteria that is defined in the "Application Requirements for a Metamodel Class" above.
The fields may be statically initialized, or they may be initialized by the provider during system initialization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The fields may be statically initialized, or they may be initialized by the provider during system initialization.
The fields must be statically initialized.


===== Conventions for Metamodel Fields

The following are conventions for static metamodel classes:

- The name of the static metamodel class should consist of underscore (`_`) followed by the entity class name.
- Fields of type `String` should be named with all capitals.
- Fields of type `Attribute` or `Attribute` subclass should be named in lower case or mixed case.
- Fields of type `String` should be named with all upper case.
- Fields of type `Attribute` (or subinterface of `Attribute`) should be named in lower case or mixed case.
gavinking marked this conversation as resolved.
Show resolved Hide resolved
- Uninitialized fields should have modifiers `public`, `static`, and `volatile`.
- `String` fields for entity attribute names should be preinitialized and have modifiers `public`, `static`, and `final`, enabling the field to be referenced by code that supplies values to annotations.
- Initialized fields should have modifiers `public`, `static`, and `final`.
- Fields of type `String` should always be statically initialized, enabling their use in annotation values.
gavinking marked this conversation as resolved.
Show resolved Hide resolved

===== Example Metamodel Class and Usage

Expand All @@ -646,9 +646,9 @@ public class _Product {
public static final String NAME = "name";
public static final String PRICE = "price";

public static volatile SortableAttribute<Product> id;
public static volatile TextAttribute<Product> name;
public static volatile SortableAttribute<Product> price;
public static final SortableAttribute<Product> id = new SortableAttributeRecord<>("id");
public static final TextAttribute<Product> name = new TextAttributeRecord<>("name");
public static final SortableAttribute<Product> price = new SortableAttributeRecord<>("price");
}
----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import jakarta.data.metamodel.SortableAttribute;
import jakarta.data.metamodel.StaticMetamodel;
import jakarta.data.metamodel.TextAttribute;
import jakarta.data.metamodel.impl.AttributeRecord;
import jakarta.data.metamodel.impl.SortableAttributeRecord;
import jakarta.data.metamodel.impl.TextAttributeRecord;

/**
* This static metamodel class tests what a user might explicitly provide,
Expand All @@ -30,11 +33,11 @@ public class _AsciiChar {
public static final String HEXADECIMAL = "hexadecimal";
public static final String NUMERICVALUE = "numericValue";

public static volatile SortableAttribute<AsciiCharacter> id;
public static volatile TextAttribute<AsciiCharacter> hexadecimal;
public static volatile Attribute<AsciiCharacter> isControl; // user decided it didn't care about sorting for this one
public static volatile SortableAttribute<AsciiCharacter> numericValue;
public static volatile TextAttribute<AsciiCharacter> thisCharacter;
public static volatile SortableAttribute<AsciiCharacter> id = new SortableAttributeRecord<>("id");
public static volatile TextAttribute<AsciiCharacter> hexadecimal = new TextAttributeRecord<>("hexadecimal");
public static volatile Attribute<AsciiCharacter> isControl = new AttributeRecord<>("isControl"); // user decided it didn't care about sorting for this one
public static volatile SortableAttribute<AsciiCharacter> numericValue = new SortableAttributeRecord<>("numericValue");
public static volatile TextAttribute<AsciiCharacter> thisCharacter = new TextAttributeRecord<>("thisCharacter");
gavinking marked this conversation as resolved.
Show resolved Hide resolved

// Avoids the checkstyle error,
// HideUtilityClassConstructor: Utility classes should not have a public or default constructor
Expand Down