Skip to content

Commit 13bb8f0

Browse files
lpandzicclaude
andcommitted
fix: support @Embedded.Empty and @Embedded.Nullable in annotation processor (#118)
The annotation processor only recognized the full @Embedded(onEmpty = ...) form. This adds detection of the shorthand @Embedded.Empty and @Embedded.Nullable annotations in both embedded field detection and prefix extraction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 15ef4a0 commit 13bb8f0

7 files changed

Lines changed: 230 additions & 7 deletions

File tree

infobip-spring-data-jdbc-annotation-processor-common/src/main/java/com/infobip/spring/data/jdbc/annotation/processor/CustomExtendedTypeFactory.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.querydsl.codegen.QueryTypeFactory;
2424
import com.querydsl.codegen.TypeMappings;
2525
import com.querydsl.codegen.utils.model.TypeCategory;
26+
import org.springframework.data.relational.core.mapping.Embedded;
2627
import org.springframework.data.relational.core.mapping.Table;
2728
import org.springframework.util.StringUtils;
2829

@@ -117,11 +118,29 @@ private String getEmbeddedPrefix(Element parentElement, Property property) {
117118
.filter(element -> element.getKind().equals(ElementKind.FIELD))
118119
.filter(element -> element.getSimpleName().toString().equals(property.getName()))
119120
.findFirst()
120-
.map(element -> element.getAnnotation(org.springframework.data.relational.core.mapping.Embedded.class))
121-
.map(org.springframework.data.relational.core.mapping.Embedded::prefix)
121+
.map(CustomExtendedTypeFactory::extractEmbeddedPrefix)
122122
.orElse("");
123123
}
124124

125+
private static String extractEmbeddedPrefix(Element element) {
126+
var embedded = element.getAnnotation(Embedded.class);
127+
if (Objects.nonNull(embedded)) {
128+
return embedded.prefix();
129+
}
130+
131+
var embeddedEmpty = element.getAnnotation(Embedded.Empty.class);
132+
if (Objects.nonNull(embeddedEmpty)) {
133+
return embeddedEmpty.prefix();
134+
}
135+
136+
var embeddedNullable = element.getAnnotation(Embedded.Nullable.class);
137+
if (Objects.nonNull(embeddedNullable)) {
138+
return embeddedNullable.prefix();
139+
}
140+
141+
return "";
142+
}
143+
125144
private Property createPrefixedProperty(Property parentProperty, Property embeddedProperty, String prefix) {
126145
var prefixedName = prefix.isEmpty() ? embeddedProperty.getName() :
127146
prefix + StringUtils.capitalize(embeddedProperty.getName());

infobip-spring-data-jdbc-annotation-processor-common/src/main/java/com/infobip/spring/data/jdbc/annotation/processor/Embeddeds.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import javax.lang.model.element.Element;
44
import javax.lang.model.util.ElementFilter;
5+
import java.lang.annotation.Annotation;
56
import java.util.Objects;
67

78
import com.querydsl.apt.Configuration;
89
import com.querydsl.codegen.Property;
10+
import org.springframework.data.relational.core.mapping.Embedded;
911

1012
class Embeddeds {
1113

@@ -24,8 +26,7 @@ static boolean isEmbedded(Configuration configuration,
2426
.filter(enclosedElement -> enclosedElement.getSimpleName()
2527
.toString()
2628
.equals(property.getName()))
27-
.allMatch(enclosedElement -> Objects.nonNull(
28-
enclosedElement.getAnnotation(embeddedAnnotation)));
29+
.allMatch(Embeddeds::hasEmbeddedAnnotation);
2930
}
3031

3132
static boolean isEmbedded(Configuration configuration, Element element) {
@@ -36,6 +37,16 @@ static boolean isEmbedded(Configuration configuration, Element element) {
3637
return false;
3738
}
3839

39-
return Objects.nonNull(element.getAnnotation(embeddedAnnotation));
40+
return hasEmbeddedAnnotation(element);
41+
}
42+
43+
private static boolean hasEmbeddedAnnotation(Element element) {
44+
return hasAnnotation(element, Embedded.class)
45+
|| hasAnnotation(element, Embedded.Empty.class)
46+
|| hasAnnotation(element, Embedded.Nullable.class);
47+
}
48+
49+
private static boolean hasAnnotation(Element element, Class<? extends Annotation> annotationType) {
50+
return Objects.nonNull(element.getAnnotation(annotationType));
4051
}
4152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.infobip.spring.data.jdbc.annotation.processor;
2+
3+
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.relational.core.mapping.Embedded;
5+
6+
@Schema("foo")
7+
public class EntityWithEmbeddedEmpty {
8+
9+
@Id
10+
private final Long id;
11+
12+
@Embedded.Empty
13+
private final EmbeddedClass embeddedClass;
14+
15+
public EntityWithEmbeddedEmpty(Long id,
16+
EmbeddedClass embeddedClass) {
17+
this.id = id;
18+
this.embeddedClass = embeddedClass;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.infobip.spring.data.jdbc.annotation.processor;
2+
3+
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.relational.core.mapping.Embedded;
5+
6+
@Schema("foo")
7+
public class EntityWithEmbeddedNullable {
8+
9+
@Id
10+
private final Long id;
11+
12+
@Embedded.Nullable
13+
private final EmbeddedClass embeddedClass;
14+
15+
public EntityWithEmbeddedNullable(Long id,
16+
EmbeddedClass embeddedClass) {
17+
this.id = id;
18+
this.embeddedClass = embeddedClass;
19+
}
20+
}

infobip-spring-data-jdbc-annotation-processor/src/test/java/com/infobip/spring/data/jdbc/annotation/processor/SpringDataJdbcAnnotationProcessorTest.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,32 @@ void shouldIgnoreMappedCollectionAnnotatedFields() {
189189
thenShouldGenerateSourceFile(actual, QEntityWithMappedCollection.class);
190190
}
191191

192+
@Test
193+
void shouldGenerateEmbeddedEmptyQClass() {
194+
// given
195+
var givenSource = givenSource(EntityWithEmbeddedEmpty.class);
196+
197+
// when
198+
var actual = whenCompile(givenSource);
199+
200+
// then
201+
thenShouldGenerateSourceFile(actual, "QEntityWithEmbeddedEmpty");
202+
thenShouldGenerateSourceFile(actual, "QEmbeddedClass");
203+
}
204+
205+
@Test
206+
void shouldGenerateEmbeddedNullableQClass() {
207+
// given
208+
var givenSource = givenSource(EntityWithEmbeddedNullable.class);
209+
210+
// when
211+
var actual = whenCompile(givenSource);
212+
213+
// then
214+
thenShouldGenerateSourceFile(actual, "QEntityWithEmbeddedNullable");
215+
thenShouldGenerateSourceFile(actual, "QEmbeddedClass");
216+
}
217+
192218
@Test
193219
void shouldGenerateMatchWithPrefixedPlayers() {
194220
// given
@@ -203,10 +229,15 @@ void shouldGenerateMatchWithPrefixedPlayers() {
203229
}
204230

205231
private void thenShouldGenerateSourceFile(Compilation actual, Class<?> typeClass) {
232+
thenShouldGenerateSourceFile(actual, typeClass.getSimpleName());
233+
}
234+
235+
private void thenShouldGenerateSourceFile(Compilation actual, String simpleClassName) {
236+
var fullName = "com.infobip.spring.data.jdbc.annotation.processor." + simpleClassName;
206237
assertThat(actual).succeeded();
207238
assertThat(actual)
208-
.generatedSourceFile(typeClass.getName())
209-
.hasSourceEquivalentTo(JavaFileObjects.forResource("expected/" + typeClass.getSimpleName() + ".java"));
239+
.generatedSourceFile(fullName)
240+
.hasSourceEquivalentTo(JavaFileObjects.forResource("expected/" + simpleClassName + ".java"));
210241
}
211242

212243
private JavaFileObject givenSource(Class<?> type) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.infobip.spring.data.jdbc.annotation.processor;
2+
3+
import static com.querydsl.core.types.PathMetadataFactory.*;
4+
5+
import com.querydsl.core.types.dsl.*;
6+
7+
import com.querydsl.core.types.PathMetadata;
8+
import javax.annotation.processing.Generated;
9+
import com.querydsl.core.types.Path;
10+
11+
import com.querydsl.sql.ColumnMetadata;
12+
import java.sql.Types;
13+
14+
15+
16+
17+
/**
18+
* QEntityWithEmbeddedEmpty is a Querydsl query type for EntityWithEmbeddedEmpty
19+
*/
20+
@Generated("com.infobip.spring.data.jdbc.annotation.processor.CustomMetaDataSerializer")
21+
public class QEntityWithEmbeddedEmpty extends com.querydsl.sql.RelationalPathBase<EntityWithEmbeddedEmpty> {
22+
23+
private static final long serialVersionUID = -229043138;
24+
25+
public static final QEntityWithEmbeddedEmpty entityWithEmbeddedEmpty = new QEntityWithEmbeddedEmpty("EntityWithEmbeddedEmpty");
26+
27+
public final NumberPath<Long> id = createNumber("id", Long.class);
28+
29+
public final StringPath foo = createString("foo");
30+
31+
public QEntityWithEmbeddedEmpty(String variable) {
32+
super(EntityWithEmbeddedEmpty.class, forVariable(variable), "foo", "EntityWithEmbeddedEmpty");
33+
addMetadata();
34+
}
35+
36+
public QEntityWithEmbeddedEmpty(String variable, String schema, String table) {
37+
super(EntityWithEmbeddedEmpty.class, forVariable(variable), schema, table);
38+
addMetadata();
39+
}
40+
41+
public QEntityWithEmbeddedEmpty(String variable, String schema) {
42+
super(EntityWithEmbeddedEmpty.class, forVariable(variable), schema, "EntityWithEmbeddedEmpty");
43+
addMetadata();
44+
}
45+
46+
public QEntityWithEmbeddedEmpty(Path<? extends EntityWithEmbeddedEmpty> path) {
47+
super(path.getType(), path.getMetadata(), "foo", "EntityWithEmbeddedEmpty");
48+
addMetadata();
49+
}
50+
51+
public QEntityWithEmbeddedEmpty(PathMetadata metadata) {
52+
super(EntityWithEmbeddedEmpty.class, metadata, "foo", "EntityWithEmbeddedEmpty");
53+
addMetadata();
54+
}
55+
56+
public void addMetadata() {
57+
addMetadata(id, ColumnMetadata.named("Id").withIndex(0));
58+
addMetadata(foo, ColumnMetadata.named("Foo").withIndex(1));
59+
}
60+
61+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.infobip.spring.data.jdbc.annotation.processor;
2+
3+
import static com.querydsl.core.types.PathMetadataFactory.*;
4+
5+
import com.querydsl.core.types.dsl.*;
6+
7+
import com.querydsl.core.types.PathMetadata;
8+
import javax.annotation.processing.Generated;
9+
import com.querydsl.core.types.Path;
10+
11+
import com.querydsl.sql.ColumnMetadata;
12+
import java.sql.Types;
13+
14+
15+
16+
17+
/**
18+
* QEntityWithEmbeddedNullable is a Querydsl query type for EntityWithEmbeddedNullable
19+
*/
20+
@Generated("com.infobip.spring.data.jdbc.annotation.processor.CustomMetaDataSerializer")
21+
public class QEntityWithEmbeddedNullable extends com.querydsl.sql.RelationalPathBase<EntityWithEmbeddedNullable> {
22+
23+
private static final long serialVersionUID = -229043138;
24+
25+
public static final QEntityWithEmbeddedNullable entityWithEmbeddedNullable = new QEntityWithEmbeddedNullable("EntityWithEmbeddedNullable");
26+
27+
public final NumberPath<Long> id = createNumber("id", Long.class);
28+
29+
public final StringPath foo = createString("foo");
30+
31+
public QEntityWithEmbeddedNullable(String variable) {
32+
super(EntityWithEmbeddedNullable.class, forVariable(variable), "foo", "EntityWithEmbeddedNullable");
33+
addMetadata();
34+
}
35+
36+
public QEntityWithEmbeddedNullable(String variable, String schema, String table) {
37+
super(EntityWithEmbeddedNullable.class, forVariable(variable), schema, table);
38+
addMetadata();
39+
}
40+
41+
public QEntityWithEmbeddedNullable(String variable, String schema) {
42+
super(EntityWithEmbeddedNullable.class, forVariable(variable), schema, "EntityWithEmbeddedNullable");
43+
addMetadata();
44+
}
45+
46+
public QEntityWithEmbeddedNullable(Path<? extends EntityWithEmbeddedNullable> path) {
47+
super(path.getType(), path.getMetadata(), "foo", "EntityWithEmbeddedNullable");
48+
addMetadata();
49+
}
50+
51+
public QEntityWithEmbeddedNullable(PathMetadata metadata) {
52+
super(EntityWithEmbeddedNullable.class, metadata, "foo", "EntityWithEmbeddedNullable");
53+
addMetadata();
54+
}
55+
56+
public void addMetadata() {
57+
addMetadata(id, ColumnMetadata.named("Id").withIndex(0));
58+
addMetadata(foo, ColumnMetadata.named("Foo").withIndex(1));
59+
}
60+
61+
}

0 commit comments

Comments
 (0)