Skip to content

Commit

Permalink
Merge pull request #258 from njr-11/230-type-safe-entity-attribute-names
Browse files Browse the repository at this point in the history
type-safe entity attributes
  • Loading branch information
otaviojava authored Oct 5, 2023
2 parents 221833e + cbf70ef commit 1d9319a
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 0 deletions.
135 changes: 135 additions & 0 deletions api/src/main/java/jakarta/data/model/Attribute.java
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>
* &#64;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();
}
}
63 changes: 63 additions & 0 deletions api/src/main/java/jakarta/data/model/AttributeInfo.java
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 api/src/main/java/jakarta/data/model/StaticMetamodel.java
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>
* &#64;Entity
* public class Person {
* &#64;Id
* public long ssn;
*
* &#64;Embedded
* public Name name;
*
* public int yearOfBirth;
* }
*
* &#64;Embeddable
* public class Name {
* public String first;
* public String last;
* }
* </pre>
*
* <p>You can define a static metamodel as follows,</p>
*
* <pre>
* &#64;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();
}
58 changes: 58 additions & 0 deletions api/src/main/java/jakarta/data/model/package-info.java
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>
* &#64;Entity
* public class Product {
* &#64;Id
* public long id;
*
* public String name;
*
* public float price;
* }
*
* &#64;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();
* }
*
* ...
*
* &#64;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;
1 change: 1 addition & 0 deletions api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@
// under: "Wildcard characters for patterns are determined by the data access provider"
module jakarta.data {
exports jakarta.data;
exports jakarta.data.model;
exports jakarta.data.page;
exports jakarta.data.repository;
exports jakarta.data.exceptions;
Expand Down
Loading

0 comments on commit 1d9319a

Please sign in to comment.