Skip to content

Commit 646bd78

Browse files
committed
Add PartitionKey and ClusteringColumn annotations and process them when parsing entities.
1 parent 4562576 commit 646bd78

13 files changed

Lines changed: 474 additions & 120 deletions

File tree

integration-tests/src/test/java/com/datastax/oss/driver/mapper/model/inventory/Product.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
package com.datastax.oss.driver.mapper.model.inventory;
1717

1818
import com.datastax.oss.driver.api.mapper.annotations.Entity;
19+
import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;
1920
import java.util.Objects;
2021
import java.util.UUID;
2122

2223
@Entity
2324
public class Product {
2425

25-
private UUID id;
26+
@PartitionKey private UUID id;
2627
private String description;
2728
private Dimensions dimensions;
2829

integration-tests/src/test/java/com/datastax/oss/driver/mapper/model/udts/Container.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.datastax.oss.driver.mapper.model.udts;
1717

1818
import com.datastax.oss.driver.api.mapper.annotations.Entity;
19+
import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;
1920
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Objects;
@@ -25,7 +26,7 @@
2526
@Entity
2627
public class Container {
2728

28-
private UUID id;
29+
@PartitionKey private UUID id;
2930
private List<Type1> list;
3031
private Map<String, List<Type1>> map1;
3132
private Map<Type1, Set<List<Type2>>> map2;

mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/entity/DefaultEntityDefinition.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ public class DefaultEntityDefinition implements EntityDefinition {
2323

2424
private final ClassName className;
2525
private final String cqlName;
26-
private final ImmutableList<PropertyDefinition> propertyDefinitions;
26+
private final List<PropertyDefinition> partitionKey;
27+
private final List<PropertyDefinition> clusteringColumns;
28+
private final ImmutableList<PropertyDefinition> regularColumns;
2729

2830
public DefaultEntityDefinition(
29-
ClassName className, String cqlName, List<PropertyDefinition> propertyDefinitions) {
31+
ClassName className, String cqlName,
32+
List<PropertyDefinition> partitionKey,
33+
List<PropertyDefinition> clusteringColumns,
34+
List<PropertyDefinition> regularColumns) {
3035
this.className = className;
3136
this.cqlName = cqlName;
32-
this.propertyDefinitions = ImmutableList.copyOf(propertyDefinitions);
37+
this.partitionKey = partitionKey;
38+
this.clusteringColumns = clusteringColumns;
39+
this.regularColumns = ImmutableList.copyOf(regularColumns);
3340
}
3441

3542
@Override
@@ -43,7 +50,17 @@ public String getCqlName() {
4350
}
4451

4552
@Override
46-
public Iterable<PropertyDefinition> getProperties() {
47-
return propertyDefinitions;
53+
public List<PropertyDefinition> getPartitionKey() {
54+
return partitionKey;
55+
}
56+
57+
@Override
58+
public List<PropertyDefinition> getClusteringColumns() {
59+
return clusteringColumns;
60+
}
61+
62+
@Override
63+
public Iterable<PropertyDefinition> getRegularColumns() {
64+
return regularColumns;
4865
}
4966
}

mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/entity/DefaultEntityFactory.java

Lines changed: 176 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@
1515
*/
1616
package com.datastax.oss.driver.internal.mapper.processor.entity;
1717

18+
import com.datastax.oss.driver.api.mapper.annotations.ClusteringColumn;
19+
import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;
1820
import com.datastax.oss.driver.internal.mapper.processor.ProcessorContext;
1921
import com.datastax.oss.driver.internal.mapper.processor.util.generation.PropertyType;
2022
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
2123
import com.squareup.javapoet.ClassName;
2224
import java.beans.Introspector;
23-
import java.util.HashMap;
24-
import java.util.Map;
25+
import java.util.List;
26+
import java.util.Set;
27+
import java.util.SortedMap;
28+
import java.util.TreeMap;
2529
import javax.lang.model.element.Element;
2630
import javax.lang.model.element.ElementKind;
2731
import javax.lang.model.element.ExecutableElement;
32+
import javax.lang.model.element.Modifier;
2833
import javax.lang.model.element.TypeElement;
2934
import javax.lang.model.element.VariableElement;
3035
import javax.lang.model.type.TypeKind;
@@ -45,78 +50,184 @@ public EntityDefinition getDefinition(TypeElement classElement) {
4550
// share the same name and operate on the same type.
4651
// This will get revisited in future tickets:
4752
// TODO support custom naming conventions
48-
// TODO property annotations: PK, custom name, computed, ignored...
53+
// TODO property annotations: custom name, computed, ignored...
4954
// TODO inherit annotations and properties from superclass / parent interface
50-
// TODO handle annotations on fields...
5155

52-
Map<String, DefaultPropertyDefinition.Builder> propertyBuilders = new HashMap<>();
56+
SortedMap<Integer, PropertyDefinition> partitionKey = new TreeMap<>();
57+
SortedMap<Integer, PropertyDefinition> clusteringColumns = new TreeMap<>();
58+
ImmutableList.Builder<PropertyDefinition> regularColumns = ImmutableList.builder();
5359
for (Element child : classElement.getEnclosedElements()) {
54-
if (child.getKind() == ElementKind.METHOD) {
55-
ExecutableElement method = (ExecutableElement) child;
56-
String methodName = method.getSimpleName().toString();
57-
if (methodName.startsWith("get") && method.getParameters().isEmpty()) {
58-
TypeMirror typeMirror = method.getReturnType();
59-
if (typeMirror.getKind() == TypeKind.VOID) {
60-
continue;
61-
}
62-
String propertyName = Introspector.decapitalize(methodName.substring(3));
63-
DefaultPropertyDefinition.Builder builder = propertyBuilders.get(propertyName);
64-
if (builder == null) {
65-
builder =
66-
new DefaultPropertyDefinition.Builder(
67-
propertyName, typeMirror, PropertyType.parse(typeMirror, context));
68-
propertyBuilders.put(propertyName, builder);
69-
} else if (!context.getTypeUtils().isSameType(builder.getRawType(), typeMirror)) {
70-
context
71-
.getMessager()
72-
.warn(
73-
method,
74-
"Ignoring method %s %s() because there is a setter "
75-
+ "with the same name but a different type: %s(%s)",
76-
typeMirror,
77-
methodName,
78-
builder.getSetterName(),
79-
builder.getRawType());
80-
continue;
81-
}
82-
builder.withGetterName(methodName);
83-
} else if (methodName.startsWith("set") && method.getParameters().size() == 1) {
84-
String propertyName = Introspector.decapitalize(methodName.substring(3));
85-
VariableElement parameter = method.getParameters().get(0);
86-
TypeMirror typeMirror = parameter.asType();
87-
DefaultPropertyDefinition.Builder builder = propertyBuilders.get(propertyName);
88-
if (builder == null) {
89-
builder =
90-
new DefaultPropertyDefinition.Builder(
91-
propertyName, typeMirror, PropertyType.parse(typeMirror, context));
92-
propertyBuilders.put(propertyName, builder);
93-
} else if (!context.getTypeUtils().isSameType(builder.getRawType(), typeMirror)) {
94-
context
95-
.getMessager()
96-
.warn(
97-
method,
98-
"Ignoring method %s(%s) because there is a getter "
99-
+ "with the same name but a different type: %s %s()",
100-
methodName,
101-
typeMirror,
102-
builder.getRawType(),
103-
builder.getGetterName());
104-
continue;
105-
}
106-
builder.withSetterName(methodName);
107-
}
60+
Set<Modifier> modifiers = child.getModifiers();
61+
if (child.getKind() != ElementKind.METHOD
62+
|| modifiers.contains(Modifier.STATIC)
63+
|| modifiers.contains(Modifier.PRIVATE)) {
64+
continue;
10865
}
109-
}
66+
ExecutableElement getMethod = (ExecutableElement) child;
67+
String getMethodName = getMethod.getSimpleName().toString();
68+
if (!getMethodName.startsWith("get") || !getMethod.getParameters().isEmpty()) {
69+
continue;
70+
}
71+
TypeMirror typeMirror = getMethod.getReturnType();
72+
if (typeMirror.getKind() == TypeKind.VOID) {
73+
continue;
74+
}
75+
String propertyName = Introspector.decapitalize(getMethodName.substring(3));
76+
String setMethodName = getMethodName.replaceFirst("get", "set");
77+
ExecutableElement setMethod = findSetMethod(classElement, setMethodName, typeMirror);
78+
if (setMethod == null) {
79+
continue; // must have both
80+
}
81+
VariableElement field = findField(classElement, propertyName, typeMirror);
82+
83+
PropertyType propertyType = PropertyType.parse(typeMirror, context);
84+
PropertyDefinition property =
85+
new DefaultPropertyDefinition(propertyName, getMethodName, setMethodName, propertyType);
11086

111-
ImmutableList.Builder<PropertyDefinition> definitions = ImmutableList.builder();
112-
for (DefaultPropertyDefinition.Builder builder : propertyBuilders.values()) {
113-
if (builder.getGetterName() != null && builder.getSetterName() != null) {
114-
definitions.add(builder.build());
87+
int partitionKeyIndex = getPartitionKeyIndex(getMethod, field);
88+
int clusteringColumnIndex = getClusteringColumnIndex(getMethod, field, partitionKeyIndex);
89+
if (partitionKeyIndex >= 0) {
90+
PropertyDefinition previous = partitionKey.putIfAbsent(partitionKeyIndex, property);
91+
if (previous != null) {
92+
context
93+
.getMessager()
94+
.error(
95+
getMethod,
96+
"Duplicate partition key index: if multiple properties are annotated "
97+
+ "with @%s, the annotation must be parameterized with an integer "
98+
+ "indicating the position. Found duplicate index %d for %s and %s.",
99+
PartitionKey.class.getSimpleName(),
100+
partitionKeyIndex,
101+
previous.getGetterName(),
102+
property.getGetterName());
103+
}
104+
} else if (clusteringColumnIndex >= 0) {
105+
PropertyDefinition previous =
106+
clusteringColumns.putIfAbsent(clusteringColumnIndex, property);
107+
if (previous != null) {
108+
context
109+
.getMessager()
110+
.error(
111+
getMethod,
112+
"Duplicate clustering column index: if multiple properties are annotated "
113+
+ "with @%s, the annotation must be parameterized with an integer "
114+
+ "indicating the position. Found duplicate index %d for %s and %s.",
115+
ClusteringColumn.class.getSimpleName(),
116+
clusteringColumnIndex,
117+
previous.getGetterName(),
118+
property.getGetterName());
119+
}
120+
} else {
121+
regularColumns.add(property);
115122
}
116123
}
117124

118125
String entityName = Introspector.decapitalize(classElement.getSimpleName().toString());
119126
return new DefaultEntityDefinition(
120-
ClassName.get(classElement), entityName, definitions.build());
127+
ClassName.get(classElement),
128+
entityName,
129+
ImmutableList.copyOf(partitionKey.values()),
130+
ImmutableList.copyOf(clusteringColumns.values()),
131+
regularColumns.build());
132+
}
133+
134+
private VariableElement findField(
135+
TypeElement classElement, String propertyName, TypeMirror fieldType) {
136+
for (Element child : classElement.getEnclosedElements()) {
137+
if (child.getKind() != ElementKind.FIELD) {
138+
continue;
139+
}
140+
VariableElement field = (VariableElement) child;
141+
if (field.getSimpleName().toString().equals(propertyName)
142+
&& context.getTypeUtils().isSameType(field.asType(), fieldType)) {
143+
return field;
144+
}
145+
}
146+
return null;
147+
}
148+
149+
private ExecutableElement findSetMethod(
150+
TypeElement classElement, String setMethodName, TypeMirror fieldType) {
151+
for (Element child : classElement.getEnclosedElements()) {
152+
Set<Modifier> modifiers = child.getModifiers();
153+
if (child.getKind() != ElementKind.METHOD
154+
|| modifiers.contains(Modifier.STATIC)
155+
|| modifiers.contains(Modifier.PRIVATE)) {
156+
continue;
157+
}
158+
ExecutableElement setMethod = (ExecutableElement) child;
159+
List<? extends VariableElement> parameters = setMethod.getParameters();
160+
161+
if (setMethod.getSimpleName().toString().equals(setMethodName)
162+
&& parameters.size() == 1
163+
&& context.getTypeUtils().isSameType(parameters.get(0).asType(), fieldType)) {
164+
return setMethod;
165+
}
166+
}
167+
return null;
168+
}
169+
170+
private int getPartitionKeyIndex(ExecutableElement getMethod, VariableElement field) {
171+
PartitionKey annotation = getMethod.getAnnotation(PartitionKey.class);
172+
PartitionKey fieldAnnotation = (field == null) ? null : field.getAnnotation(PartitionKey.class);
173+
174+
if (annotation != null) {
175+
if (fieldAnnotation != null) {
176+
context
177+
.getMessager()
178+
.warn(
179+
field,
180+
"@%s should be used either on the field or the getter, but not both. "
181+
+ "The annotation on this field will be ignored.",
182+
PartitionKey.class.getSimpleName());
183+
}
184+
return annotation.value();
185+
} else if (fieldAnnotation != null) {
186+
return fieldAnnotation.value();
187+
} else {
188+
return -1;
189+
}
190+
}
191+
192+
private int getClusteringColumnIndex(
193+
ExecutableElement getMethod, VariableElement field, int partitionKeyIndex) {
194+
ClusteringColumn annotation = getMethod.getAnnotation(ClusteringColumn.class);
195+
ClusteringColumn fieldAnnotation =
196+
(field == null) ? null : field.getAnnotation(ClusteringColumn.class);
197+
198+
if (annotation != null) {
199+
if (partitionKeyIndex >= 0) {
200+
context
201+
.getMessager()
202+
.error(
203+
getMethod,
204+
"Properties can't be annotated with both @%s and @%s.",
205+
PartitionKey.class.getSimpleName(),
206+
ClusteringColumn.class.getSimpleName());
207+
return -1;
208+
} else if (fieldAnnotation != null) {
209+
context
210+
.getMessager()
211+
.warn(
212+
field,
213+
"@%s should be used either on the field or the getter, but not both. "
214+
+ "The annotation on this field will be ignored.",
215+
ClusteringColumn.class.getSimpleName());
216+
}
217+
return annotation.value();
218+
} else if (fieldAnnotation != null) {
219+
if (partitionKeyIndex >= 0) {
220+
context
221+
.getMessager()
222+
.error(
223+
field,
224+
"Properties can't be annotated with both @%s and @%s.",
225+
PartitionKey.class.getSimpleName(),
226+
ClusteringColumn.class.getSimpleName());
227+
}
228+
return fieldAnnotation.value();
229+
} else {
230+
return -1;
231+
}
121232
}
122233
}

0 commit comments

Comments
 (0)