diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java index b3f821dba..dbae7dae0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java @@ -87,8 +87,10 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { IAnnotationBinding repoAnnotationType) { ITypeBinding domainClass = getTypeFromAnnotationParameter(repoAnnotationType, "domainClass"); if (domainClass != null) { - ITypeBinding idType = findIdType(domainClass); - if (idType != null) { + List idTypes = findIdType(domainClass); + // Only support single id type. If more than one is present that is a composite key for which we don't mark anything yet + if (idTypes.size() == 1) { + ITypeBinding idType = idTypes.get(0); ITypeBinding repoIdType = getTypeFromAnnotationParameter(repoAnnotationType, "idClass"); if (repoIdType != null && !isValidRepoIdType(repoIdType, idType)) { ASTUtils.getAttribute(annotationOverClass, "idClass").ifPresentOrElse( @@ -181,9 +183,10 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { } } if (domainClassType != null) { - ITypeBinding domainIdType = findIdType(domainClassType); - if (domainIdType != null && !isValidRepoIdType(idType, domainIdType)) { - + List domainIdTypes = findIdType(domainClassType); + // Only support single id type. If more than one is present that is a composite key for which we don't mark anything yet + if (domainIdTypes.size() == 1 && !isValidRepoIdType(idType, domainIdTypes.get(0))) { + ITypeBinding domainIdType = domainIdTypes.get(0); if (isNoNewTypeParamsAdded(repoTypeChain)) { List matchedParams = findParamTypes(typeDecl.typeParameters(), idType); if (matchedParams.size() > 1) { @@ -237,12 +240,15 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { return null; } - private ITypeBinding findIdType(ITypeBinding type) { - ITypeBinding idType = findAnnotatedIdType(type, new HashSet<>()); - if (idType == null && considerIdField) { - idType = findIdFieldType(type); + private List findIdType(ITypeBinding type) { + List idTypes = findAnnotatedIdTypes(type, new HashSet<>()); + if (idTypes.isEmpty() && considerIdField) { + ITypeBinding idType = findIdFieldType(type); + if (idType != null) { + idTypes.add(idType); + } } - return idType; + return idTypes; } private boolean isValidRepoIdType(ITypeBinding repoIdType, ITypeBinding idType) { @@ -323,26 +329,29 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { return null; } - private static ITypeBinding findAnnotatedIdType(ITypeBinding type, Set visited) { + private static List findAnnotatedIdTypes(ITypeBinding type, Set visited) { + List idTypes = new ArrayList<>(); List idAnnotations = List.of(Annotations.SPRING_ENTITY_ID, Annotations.JPA_JAKARTA_ENTITY_ID, Annotations.JPA_JAVAX_ENTITY_ID); for (IVariableBinding m : type.getDeclaredFields()) { String s = fieldSignature(m); if (!visited.contains(s) && isAnnotationCompatible(m.getAnnotations(), idAnnotations)) { - return m.getType(); + idTypes.add(m.getType()); } visited.add(s); } - for (IMethodBinding m : type.getDeclaredMethods()) { - String s = methodSignature(m); - if (!visited.contains(s) && isAnnotationCompatible(m.getAnnotations(), idAnnotations)) { - return m.getReturnType(); + if (!type.isRecord()) { + for (IMethodBinding m : type.getDeclaredMethods()) { + String s = methodSignature(m); + if (!visited.contains(s) && isAnnotationCompatible(m.getAnnotations(), idAnnotations)) { + idTypes.add(m.getReturnType()); + } + visited.add(s); + } + if (type.getSuperclass() != null) { + idTypes.addAll(findAnnotatedIdTypes(type.getSuperclass(), visited)); } - visited.add(s); } - if (type.getSuperclass() != null) { - return findAnnotatedIdType(type.getSuperclass(), visited); - } - return null; + return idTypes; } private static String fieldSignature(IVariableBinding f) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/EntityIdForRepoReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/EntityIdForRepoReconcilerTest.java index d0006d965..4cba48d44 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/EntityIdForRepoReconcilerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/EntityIdForRepoReconcilerTest.java @@ -1046,4 +1046,37 @@ public class EntityIdForRepoReconcilerTest extends BaseReconcilerTest { } + @Test + void compositeId() throws Exception { + + Path customerSource = createFile("Customer.java", """ + package demo; + + import org.springframework.data.annotation.Id; + + public class Customer { + @Id String id; + @Id String id_additional; + } + """); + + Path customerId = createFile("CustomerId.java", """ + package demo; + + public record CustomerId(String id, String id_additional) {} + """); + + String source = """ + package demo; + + import org.springframework.data.repository.Repository; + + interface CustomerRepository extends Repository {} + """; + List problems = reconcile("CustomerRepository.java", source, false, customerSource, customerId); + + assertEquals(0, problems.size()); + + } + }