Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ public String paramAnnosForUpdate() {
return By.class.getName();
}

@Override
@Trivial
public String persistenceFeatureName() {
return "persistence-3.2";
}

@Override
@Trivial
public Set<Class<?>> resourceAccessorTypes(boolean stateful) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ public String paramAnnosForUpdate() {
SubtractFrom.class.getSimpleName();
}

@Override
@Trivial
public String persistenceFeatureName() {
return "persistence-4.0";
}

@Override
@Trivial
public Set<Class<?>> resourceAccessorTypes(boolean stateful) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1220,3 +1220,24 @@ CWWKD1120.cursor.keyword.mismatch.explanation=Keywords such as GROUP BY, HAVING,
pagination.
CWWKD1120.cursor.keyword.mismatch.useraction=Rewrite the query to include only \
the clauses: SELECT, FROM, and WHERE.

CWWKD1121.record.lacks.id=CWWKD1121E: The {0} record cannot be used as an entity \
because it lacks a unqiue identifier. One of the record components ({1}) must \
Copy link

@helyarp helyarp Dec 5, 2025

Choose a reason for hiding this comment

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

Instead of One of the record components ({1}), change it to The ({1}) record component

Copy link
Member Author

Choose a reason for hiding this comment

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

@helyarp thanks for reviewing so quickly.
{1} is actually a list of the record components, so this will look like,

CWWKD1121E: The org.example.Person record cannot be used as an entity
because it lacks a unqiue identifier. One of the record components (ssn, firstName, lastName, birthday) must ...

so it would be incorrect to say "The record component" because the value has multiple.

Copy link

Choose a reason for hiding this comment

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

@njr-11 Hi, thanks for the explanation. Keep what you have. I'll add the ID reviewed label.

be named id, have a name that ends with _id, Id or ID, or be of the type \
java.util.UUID.
CWWKD1121.record.lacks.id.explanation=Entities are required to have a unique \
identifier attribute.
CWWKD1121.record.lacks.id.useraction=Add or rename a record component to \
follow one of the stated conventions for assigning a unique identifier.

CWWKD1122.entity.lacks.id=CWWKD1122E: The {0} class cannot be used as an \
entity because it lacks a unique identifier. To assign a unique identifier, \
ensure the {1} feature is enabled and add the @jakarta.persistence.Entity annotation \
to the entity class and add the @jakarta.persistence.Id annotation to at most one \
field or getter method of the entity class. \
Alternatively, use a META-INF/orm.xml file to \
define the entity and its unique identifier.
CWWKD1122.entity.lacks.id.explanation=Entities are required to have a unique \
identifier attribute.
CWWKD1122.entity.lacks.id.useraction=Enable the Jakarta Persistence feature \
and follow the Jakarta Persistence entity model to assign a unique identifier.
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.ibm.websphere.ras.Tr;
import com.ibm.websphere.ras.TraceComponent;
import com.ibm.websphere.ras.annotation.Trivial;

import io.openliberty.data.internal.persistence.DataProvider;
import io.openliberty.data.internal.persistence.Util;
import io.openliberty.data.internal.persistence.orm.Models.AccessType;
import io.openliberty.data.internal.persistence.orm.Models.Attribute;
Expand Down Expand Up @@ -90,6 +92,7 @@ public class EntityParser {
private final LinkedHashSet<String> classNames;

// Global configurations
private final DataProvider provider;
private final String tablePrefix;

// State controls flow from initialization to generation
Expand All @@ -99,8 +102,14 @@ public class EntityParser {
private Class<?> currentEntity;
private Set<Attribute> idAttributes;

/**
* Construct a new entity parser.
*
* @param tablePrefix the table prefix or empty string if none.
* @param provider OSGi service representing this Data provider.
*/
@Trivial
public EntityParser(String tablePrefix) {
public EntityParser(String tablePrefix, DataProvider provider) {
this.mappedSuperclasses = new HashMap<>();
this.entities = new HashMap<>();
this.embeddables = new HashMap<>();
Expand All @@ -113,6 +122,7 @@ public EntityParser(String tablePrefix) {
this.tableNames = new LinkedHashSet<>();
this.classNames = new LinkedHashSet<>();

this.provider = provider;
this.tablePrefix = tablePrefix;

this.doneParsing = false;
Expand Down Expand Up @@ -543,17 +553,24 @@ private void foundConverter(Convert convert) {
@Trivial
private void verify() {
if (idAttributes.isEmpty()) {
EntityRecord invalid = entities.get(currentEntity);
Set<Class<?>> supers = entitiesSuperclasses.get(currentEntity);
Set<MappedSuperclass> invalidSupers = supers == null || supers.isEmpty() ? //
Set.of() : //
supers.stream() //
.map(c -> mappedSuperclasses.get(c))//
.collect(Collectors.toSet());

//TODO NLS
throw new MappingException("The entity " + invalid + " had no id attribute"
+ (invalidSupers.isEmpty() ? " " : " nor was any id attribute found on any mapped superclass " + invalidSupers));
Class<?> recordEntityClass = entityToRecord.get(currentEntity);
if (recordEntityClass == null) {
throw exc(MappingException.class,
"CWWKD1122.entity.lacks.id",
currentEntity.getName(),
provider.compat.persistenceFeatureName());
} else { // a Java record entity
String recordComponentNames = Stream //
.of(recordEntityClass.getRecordComponents()) //
.map(RecordComponent::getName) //
.reduce("", (s, n) -> s.length() == 0 //
? n //
: (s + ", " + n));
throw exc(MappingException.class,
"CWWKD1121.record.lacks.id",
recordEntityClass.getName(),
recordComponentNames);
}
}

if (idAttributes.size() > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ public DBStoreEMBuilder(DataProvider provider,

ArrayList<InMemoryMappingFile> generatedEntities = new ArrayList<InMemoryMappingFile>();

EntityParser parser = new EntityParser(tablePrefix);
EntityParser parser = new EntityParser(tablePrefix, provider);

for (Class<?> c : entityTypes) {
if (c.isAnnotationPresent(Entity.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,38 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Map;

import org.junit.Test;

import io.openliberty.data.internal.persistence.DataProvider;
import io.openliberty.data.internal.persistence.orm.TestConverters.InvalidConverter;
import jakarta.data.exceptions.MappingException;

/**
*
* Unit testing of error paths in EntityParser.
*/
public class EntityParserErrorTests {
private final DataProvider provider;

public EntityParserErrorTests() {
provider = new DataProvider(//
Map.of(), // properties
null, // CDIService
null, // ClassLoaderIdentifierService
new MockVersionCompatibility(), //
null, // ConfigurationAdmin
null, // ExecutorService
null, // LocalTransactionCurrent
null, // MetaDataIdentifierService
null, // ResourceConfigFactory
null // EmbeddableWebSphereTransactionManager
);
}

@Test
public void noIdEntityTest() {
EntityParser p = new EntityParser("");
EntityParser p = new EntityParser("", provider);

try {
p.parseUnannotatedEntity(WithoutId.class);
Expand All @@ -37,23 +56,25 @@ public void noIdEntityTest() {

@Test
public void noIdInMappedSuperclassEntityTest() {
EntityParser p = new EntityParser("");
EntityParser p = new EntityParser("", provider);

try {
p.parseUnannotatedEntity(WithoutIdMappedSuperclass.class);
fail("Should not have been able to parse an entity without an id atribute");
} catch (MappingException e) {
assertTrue("Error message should have contained entity class name " + WithoutIdMappedSuperclass.class.getName() + " but was " + e.getMessage(),
e.getMessage().contains("WithoutIdMappedSuperclass"));
assertTrue("The CWWKD1122E error message should be used,",
e.getMessage().startsWith("CWWKD1122E:"));

assertTrue("Error message should have contained mappedsuperclass name " + SuperAlpha.class.getName() + " but was " + e.getMessage(),
e.getMessage().contains("SuperAlpha"));
assertTrue("Error message should have contained entity class name " +
WithoutIdMappedSuperclass.class.getName() +
" but was " + e.getMessage(),
e.getMessage().contains("WithoutIdMappedSuperclass"));
}
}

@Test
public void multipleIdInMappedSuperclassEntityTest() {
EntityParser p = new EntityParser("");
EntityParser p = new EntityParser("", provider);

try {
p.parseUnannotatedEntity(WithMultipleIds.class);
Expand All @@ -69,7 +90,7 @@ public void multipleIdInMappedSuperclassEntityTest() {

@Test
public void invalidConverterEntityTest() {
EntityParser p = new EntityParser("");
EntityParser p = new EntityParser("", provider);

try {
p.parseUnannotatedEntity(WithConverterInvalid.class);
Expand Down
Loading