-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #258 from njr-11/230-type-safe-entity-attribute-names
type-safe entity attributes
- Loading branch information
Showing
6 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package jakarta.data.model; | ||
|
||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import jakarta.data.Sort; | ||
import jakarta.data.exceptions.MappingException; | ||
|
||
/** | ||
* Represents an entity attribute in the {@link StaticMetamodel}. | ||
*/ | ||
public class Attribute { | ||
// This allows the Jakarta Data provider to initialize the static final | ||
// Attribute instance with the provider's own implementation of it. | ||
private final AtomicReference<AttributeInfo> info = new AtomicReference<AttributeInfo>(); | ||
|
||
private Attribute() { | ||
} | ||
|
||
/** | ||
* Obtain a request for an ascending {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for an ascending sort on the entity attribute. | ||
* @throws MappingException if an entity attribute with this name does not exist or | ||
* if no Jakarta Data provider provides a repository for the entity type. | ||
*/ | ||
public final Sort asc() { | ||
return attrInfo().asc(); | ||
} | ||
|
||
/** | ||
* Obtain a request for an ascending, case insensitive {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for an ascending, case insensitive sort on the entity attribute. | ||
* @throws MappingException if an entity attribute with this name does not exist or | ||
* if no Jakarta Data provider provides a repository for the entity type. | ||
*/ | ||
public final Sort ascIgnoreCase() { | ||
return attrInfo().ascIgnoreCase(); | ||
} | ||
|
||
private final AttributeInfo attrInfo() { | ||
AttributeInfo attrInfo = info.get(); | ||
if (attrInfo == null) | ||
throw new MappingException("Unable to find a Jakarta Data provider that provides a repository " + | ||
" for an entity with an attribute having this name."); | ||
return attrInfo; | ||
} | ||
|
||
/** | ||
* Obtain a request for a descending {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for a descending sort on the entity attribute. | ||
* @throws MappingException if an entity attribute with this name does not exist or | ||
* if no Jakarta Data provider provides a repository for the entity type. | ||
*/ | ||
public final Sort desc() { | ||
return attrInfo().desc(); | ||
} | ||
|
||
/** | ||
* Obtain a request for a descending, case insensitive {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for a descending, case insensitive sort on the entity attribute. | ||
* @throws MappingException if an entity attribute with this name does not exist or | ||
* if no Jakarta Data provider provides a repository for the entity type. | ||
*/ | ||
public final Sort descIgnoreCase() { | ||
return attrInfo().descIgnoreCase(); | ||
} | ||
|
||
/** | ||
* <p>Obtains a new instance for the Jakarta Data provider to initialize.</p> | ||
* | ||
* <p>The Jakarta Data provider automatically initializes the instance when | ||
* used as the value of a {@code public}, {@code static}, {@code final} field | ||
* of a class that is annotated with {@link StaticMetamodel}, | ||
* where the field name matches the name of an entity attribute. | ||
* For example, a {@code Vehicle} entity with attributes {@code vin}, {@code make}, | ||
* {@code model}, and {@code year} could have the following static metamodel,</p> | ||
* | ||
* <pre> | ||
* @StaticMetamodel(Vehicle.class) | ||
* public class Vehicle_ { | ||
* public static final Attribute vin = Attribute.get(); | ||
* public static final Attribute make = Attribute.get(); | ||
* public static final Attribute model = Attribute.get(); | ||
* public static final Attribute year = Attribute.get(); | ||
* } | ||
* </pre> | ||
* | ||
* @return a new instance for an entity attribute. | ||
*/ | ||
public static final Attribute get() { | ||
return new Attribute(); | ||
} | ||
|
||
/** | ||
* Used by the Jakarta Data provider to initialize this {@code Attribute} with implementation. | ||
* | ||
* @param attrInfo attribute information provided by the Jakarta Data provider. | ||
* @return true if initialization was successful; false if previously initialized. | ||
*/ | ||
public final boolean init(AttributeInfo attrInfo) { | ||
return info.compareAndSet(null, attrInfo); | ||
} | ||
|
||
/** | ||
* Obtain the entity attribute name, suitable for use wherever the specification requires | ||
* an entity attribute name. For example, as the parameter to {@link Sort#asc(String)}. | ||
* | ||
* @return the entity attribute name. | ||
* @throws MappingException if an entity attribute with this name does not exist or | ||
* if no Jakarta Data provider provides a repository for the entity type. | ||
*/ | ||
public final String name() { | ||
return attrInfo().name(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package jakarta.data.model; | ||
|
||
import jakarta.data.Sort; | ||
|
||
/** | ||
* Implemented by the Jakarta Data provider to | ||
* {@link Attribute#init(AttributeInfo) initialize} an attribute field | ||
* in the {@link StaticMetamodel}. | ||
*/ | ||
public interface AttributeInfo { | ||
/** | ||
* Returns a request for an ascending {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for an ascending {@link Sort} based on the entity attribute. | ||
*/ | ||
Sort asc(); | ||
|
||
/** | ||
* Returns a request for an ascending, case insensitive {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for an ascending, case insensitive sort on the entity attribute. | ||
*/ | ||
Sort ascIgnoreCase(); | ||
|
||
/** | ||
* Returns a request for a descending {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for a descending {@link Sort} based on the entity attribute. | ||
*/ | ||
Sort desc(); | ||
|
||
/** | ||
* Returns a request for a descending, case insensitive {@link Sort} based on the entity attribute. | ||
* | ||
* @return a request for a descending, case insensitive sort on the entity attribute. | ||
*/ | ||
Sort descIgnoreCase(); | ||
|
||
/** | ||
* Returns the name of the entity attribute, suitable for use wherever the specification requires | ||
* an entity attribute name. For example, as the parameter to {@link Sort#asc(String)}. | ||
* | ||
* @return the entity attribute name. | ||
*/ | ||
String name(); | ||
} |
113 changes: 113 additions & 0 deletions
113
api/src/main/java/jakarta/data/model/StaticMetamodel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package jakarta.data.model; | ||
|
||
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; | ||
|
||
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>For example, for the following entity,</p> | ||
* | ||
* <pre> | ||
* @Entity | ||
* public class Person { | ||
* @Id | ||
* public long ssn; | ||
* | ||
* @Embedded | ||
* public Name name; | ||
* | ||
* public int yearOfBirth; | ||
* } | ||
* | ||
* @Embeddable | ||
* public class Name { | ||
* public String first; | ||
* public String last; | ||
* } | ||
* </pre> | ||
* | ||
* <p>You can define a static metamodel as follows,</p> | ||
* | ||
* <pre> | ||
* @StaticMetamodel(Person.class) | ||
* public class Person_ { | ||
* public static final Attribute ssn = Attribute.get(); // ssn or id | ||
* public static final Attribute name = Attribute.get(); | ||
* public static final Attribute name_first = Attribute.get(); | ||
* public static final Attribute name_last = Attribute.get(); | ||
* public static final Attribute yearOfBirth = Attribute.get(); | ||
* } | ||
* </pre> | ||
* | ||
* <p>And use it to refer to entity attributes in a type-safe manner,</p> | ||
* | ||
* <pre> | ||
* pageRequest = Pageable.ofSize(20).sortBy(Person_.yearOfBirth.desc(), | ||
* Person_.name_last.asc(), | ||
* Person_.name_first.asc(), | ||
* Person_.ssn.asc()); | ||
* </pre> | ||
* | ||
* <p>When a class is annotated as a {@code StaticMetamodel}, 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 type is {@link Attribute}.</li> | ||
* <li>The field is {@code public}.</li> | ||
* <li>The field is {@code static}.</li> | ||
* <li>The field is {@code final}.</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> | ||
* </ul> | ||
* | ||
* <p>The Jakarta Data provider must {@link Attribute#init(AttributeInfo) initialize} | ||
* each {@code Attribute} value that corresponds to the name of an entity attribute.</p> | ||
* | ||
* <p>Additionally, a field that meets the above criteria except for the name | ||
* and is named {@code id} must be assigned by the Jakarta Data provider to the | ||
* unique identifier entity attribute if a single entity attribute represents the | ||
* unique identifier.</p> | ||
* | ||
* <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 | ||
* initialize the {@code Attribute} fields of the class that is annotated with | ||
* {@code StaticMetamodel}.</p> | ||
*/ | ||
@Documented | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface StaticMetamodel { | ||
/** | ||
* An entity class. | ||
* | ||
* @return the entity class. | ||
*/ | ||
Class<?> value(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/** | ||
* <p>A static metamodel for entities that are used in Jakarta Data repositories.</p> | ||
* | ||
* <p>The {@link StaticMetamodel} allows for type-safe operations that avoid the | ||
* need to hard-code entity attribute names as Strings. For example,</p> | ||
* | ||
* <pre> | ||
* @Entity | ||
* public class Product { | ||
* @Id | ||
* public long id; | ||
* | ||
* public String name; | ||
* | ||
* public float price; | ||
* } | ||
* | ||
* @StaticMetamodel(Product.class) | ||
* public class Product_ { | ||
* public static final Attribute id = Attribute.get(); | ||
* public static final Attribute name = Attribute.get(); | ||
* public static final Attribute price = Attribute.get(); | ||
* } | ||
* | ||
* ... | ||
* | ||
* @Repository | ||
* Product products; | ||
* | ||
* ... | ||
* | ||
* Pageable pageRequest = Pageable.ofSize(20) | ||
* .sortBy(Product_price.desc(), | ||
* Product_name.asc(), | ||
* Product_id.asc()); | ||
* | ||
* page1 = products.findByNameLike(namePattern, pageRequest); | ||
* </pre> | ||
*/ | ||
package jakarta.data.model; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.