Skip to content

Commit b376aa4

Browse files
visualagerozza
authored andcommitted
BsonCreator and BsonProperty improvements
Added support for using BsonId with the BsonCreator. Allow BsonProperty to rename properties. Default using the write name for the BsonProperty, if not found fallback to existing getter. JAVA-2599
1 parent 17a80a2 commit b376aa4

File tree

8 files changed

+310
-19
lines changed

8 files changed

+310
-19
lines changed

bson/src/main/org/bson/codecs/pojo/ClassModelBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* @see ClassModel
4444
*/
4545
public class ClassModelBuilder<T> {
46-
private static final String ID_PROPERTY_NAME = "_id";
46+
static final String ID_PROPERTY_NAME = "_id";
4747
private final List<PropertyModelBuilder<?>> propertyModelBuilders = new ArrayList<PropertyModelBuilder<?>>();
4848
private InstanceCreatorFactory<T> instanceCreatorFactory;
4949
private Class<T> type;

bson/src/main/org/bson/codecs/pojo/ConventionAnnotationImpl.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,24 +136,57 @@ private <T> void processCreatorAnnotation(final ClassModelBuilder<T> classModelB
136136
throw creatorExecutable.getError(clazz, "All parameters must be annotated with a @BsonProperty in %s");
137137
}
138138
for (int i = 0; i < properties.size(); i++) {
139-
BsonProperty bsonProperty = properties.get(i);
139+
boolean isIdProperty = creatorExecutable.getIdPropertyIndex() != null && creatorExecutable.getIdPropertyIndex().equals(i);
140140
Class<?> parameterType = parameterTypes.get(i);
141-
PropertyModelBuilder<?> propertyModelBuilder = classModelBuilder.getProperty(bsonProperty.value());
142-
if (propertyModelBuilder == null) {
143-
addCreatorPropertyToClassModelBuilder(classModelBuilder, bsonProperty.value(), parameterType);
144-
} else if (!propertyModelBuilder.getTypeData().isAssignableFrom(parameterType)) {
141+
PropertyModelBuilder<?> propertyModelBuilder = null;
142+
143+
if (isIdProperty) {
144+
propertyModelBuilder = classModelBuilder.getProperty(classModelBuilder.getIdPropertyName());
145+
} else {
146+
BsonProperty bsonProperty = properties.get(i);
147+
148+
// Find the property using write name and falls back to read name
149+
for (PropertyModelBuilder<?> builder : classModelBuilder.getPropertyModelBuilders()) {
150+
if (bsonProperty.value().equals(builder.getWriteName())) {
151+
propertyModelBuilder = builder;
152+
break;
153+
} else if (bsonProperty.value().equals(builder.getReadName())) {
154+
// When there is a property that matches the read name of the parameter, save it but continue to look
155+
// This is just in case there is another property that matches the write name.
156+
propertyModelBuilder = builder;
157+
}
158+
}
159+
160+
// Support legacy options, when BsonProperty matches the actual POJO property name (e.g. method name or field name).
161+
if (propertyModelBuilder == null) {
162+
propertyModelBuilder = classModelBuilder.getProperty(bsonProperty.value());
163+
}
164+
165+
if (propertyModelBuilder == null) {
166+
propertyModelBuilder = addCreatorPropertyToClassModelBuilder(classModelBuilder, bsonProperty.value(),
167+
parameterType);
168+
} else {
169+
// An existing property is found, set its write name
170+
propertyModelBuilder.writeName(bsonProperty.value());
171+
}
172+
}
173+
174+
if (!propertyModelBuilder.getTypeData().isAssignableFrom(parameterType)) {
145175
throw creatorExecutable.getError(clazz, format("Invalid Property type for '%s'. Expected %s, found %s.",
146-
bsonProperty.value(), propertyModelBuilder.getTypeData().getType(), parameterType));
176+
propertyModelBuilder.getWriteName(), propertyModelBuilder.getTypeData().getType(), parameterType));
147177
}
148178
}
149179
classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl<T>(creatorExecutable));
150180
}
151181
}
152182

153-
private <T, S> void addCreatorPropertyToClassModelBuilder(final ClassModelBuilder<T> classModelBuilder, final String name,
154-
final Class<S> clazz) {
155-
classModelBuilder.addProperty(createPropertyModelBuilder(new PropertyMetadata<S>(name, classModelBuilder.getType().getSimpleName(),
156-
TypeData.builder(clazz).build())).readName(null).writeName(name));
183+
private <T, S> PropertyModelBuilder<S> addCreatorPropertyToClassModelBuilder(final ClassModelBuilder<T> classModelBuilder,
184+
final String name,
185+
final Class<S> clazz) {
186+
PropertyModelBuilder<S> propertyModelBuilder = createPropertyModelBuilder(new PropertyMetadata<S>(name,
187+
classModelBuilder.getType().getSimpleName(), TypeData.builder(clazz).build())).readName(null).writeName(name);
188+
classModelBuilder.addProperty(propertyModelBuilder);
189+
return propertyModelBuilder;
157190
}
158191

159192
private void cleanPropertyBuilders(final ClassModelBuilder<?> classModelBuilder) {

bson/src/main/org/bson/codecs/pojo/CreatorExecutable.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.bson.codecs.pojo;
1818

1919
import org.bson.codecs.configuration.CodecConfigurationException;
20+
import org.bson.codecs.pojo.annotations.BsonId;
2021
import org.bson.codecs.pojo.annotations.BsonProperty;
2122

2223
import java.lang.annotation.Annotation;
@@ -33,6 +34,7 @@ final class CreatorExecutable<T> {
3334
private final Constructor<T> constructor;
3435
private final Method method;
3536
private final List<BsonProperty> properties = new ArrayList<BsonProperty>();
37+
private final Integer idPropertyIndex;
3638
private final List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
3739

3840
CreatorExecutable(final Class<T> clazz, final Constructor<T> constructor) {
@@ -47,32 +49,47 @@ private CreatorExecutable(final Class<T> clazz, final Constructor<T> constructor
4749
this.clazz = clazz;
4850
this.constructor = constructor;
4951
this.method = method;
52+
Integer idPropertyIndex = null;
5053

5154
if (constructor != null || method != null) {
5255
parameterTypes.addAll(asList(constructor != null ? constructor.getParameterTypes() : method.getParameterTypes()));
5356
Annotation[][] parameterAnnotations = constructor != null ? constructor.getParameterAnnotations()
5457
: method.getParameterAnnotations();
5558

56-
for (Annotation[] parameterAnnotation : parameterAnnotations) {
59+
for (int i = 0; i < parameterAnnotations.length; ++i) {
60+
Annotation[] parameterAnnotation = parameterAnnotations[i];
61+
5762
for (Annotation annotation : parameterAnnotation) {
5863
if (annotation.annotationType().equals(BsonProperty.class)) {
5964
properties.add((BsonProperty) annotation);
6065
break;
6166
}
67+
68+
if (annotation.annotationType().equals(BsonId.class)) {
69+
properties.add(null);
70+
idPropertyIndex = i;
71+
break;
72+
}
6273
}
6374
}
6475
}
76+
77+
this.idPropertyIndex = idPropertyIndex;
6578
}
6679

67-
public Class<T> getType() {
80+
Class<T> getType() {
6881
return clazz;
6982
}
7083

7184
List<BsonProperty> getProperties() {
7285
return properties;
7386
}
7487

75-
public List<Class<?>> getParameterTypes() {
88+
Integer getIdPropertyIndex() {
89+
return idPropertyIndex;
90+
}
91+
92+
List<Class<?>> getParameterTypes() {
7693
return parameterTypes;
7794
}
7895

@@ -109,7 +126,7 @@ CodecConfigurationException getError(final Class<?> clazz, final String msg) {
109126
return getError(clazz, constructor != null, msg);
110127
}
111128

112-
void checkHasAnExecutable() {
129+
private void checkHasAnExecutable() {
113130
if (constructor == null && method == null) {
114131
throw new CodecConfigurationException(format("Cannot find a public constructor for '%s'.", clazz.getSimpleName()));
115132
}

bson/src/main/org/bson/codecs/pojo/InstanceCreatorImpl.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ final class InstanceCreatorImpl<T> implements InstanceCreator<T> {
4141
} else {
4242
this.cachedValues = new HashMap<PropertyModel<?>, Object>();
4343
this.properties = new HashMap<String, Integer>();
44+
45+
if (creatorExecutable.getIdPropertyIndex() != null) {
46+
this.properties.put(ClassModelBuilder.ID_PROPERTY_NAME, creatorExecutable.getIdPropertyIndex());
47+
}
48+
4449
for (int i = 0; i < creatorExecutable.getProperties().size(); i++) {
45-
this.properties.put(creatorExecutable.getProperties().get(i).value(), i);
50+
if (creatorExecutable.getIdPropertyIndex() == null || !creatorExecutable.getIdPropertyIndex().equals(i)) {
51+
// Skip the ID property
52+
this.properties.put(creatorExecutable.getProperties().get(i).value(), i);
53+
}
4654
}
55+
4756
this.params = new Object[properties.size()];
4857
}
4958
}
@@ -54,11 +63,18 @@ public <S> void set(final S value, final PropertyModel<S> propertyModel) {
5463
propertyModel.getPropertyAccessor().set(newInstance, value);
5564
} else {
5665
if (!properties.isEmpty()) {
57-
Integer index = properties.get(propertyModel.getName());
66+
String propertyName = propertyModel.getWriteName();
67+
68+
if (!properties.containsKey(propertyName)) {
69+
// Support legacy BsonProperty settings where the property name was used instead of the write name.
70+
propertyName = propertyModel.getName();
71+
}
72+
73+
Integer index = properties.get(propertyName);
5874
if (index != null) {
5975
params[index] = value;
6076
}
61-
properties.remove(propertyModel.getName());
77+
properties.remove(propertyName);
6278
}
6379

6480
if (properties.isEmpty()) {

bson/src/main/org/bson/codecs/pojo/annotations/BsonId.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
*/
3333
@Documented
3434
@Retention(RetentionPolicy.RUNTIME)
35-
@Target({ElementType.FIELD, ElementType.METHOD})
35+
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
3636
public @interface BsonId {
3737
}

bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
import org.bson.codecs.pojo.entities.SimpleNestedPojoModel;
5656
import org.bson.codecs.pojo.entities.UpperBoundsConcreteModel;
5757
import org.bson.codecs.pojo.entities.conventions.CreatorAllFinalFieldsModel;
58+
import org.bson.codecs.pojo.entities.conventions.CreatorConstructorIdModel;
5859
import org.bson.codecs.pojo.entities.conventions.CreatorConstructorModel;
60+
import org.bson.codecs.pojo.entities.conventions.CreatorConstructorRenameModel;
5961
import org.bson.codecs.pojo.entities.conventions.CreatorMethodModel;
6062
import org.bson.codecs.pojo.entities.conventions.CreatorNoArgsConstructorModel;
6163
import org.bson.codecs.pojo.entities.conventions.CreatorNoArgsMethodModel;
@@ -231,6 +233,14 @@ private static List<TestData> testCases() {
231233
getPojoCodecProviderBuilder(CreatorConstructorModel.class),
232234
"{'integersField': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}"));
233235

236+
data.add(new TestData("Creator constructor with rename", new CreatorConstructorRenameModel(asList(10, 11), "twelve", 13),
237+
getPojoCodecProviderBuilder(CreatorConstructorRenameModel.class),
238+
"{'integerList': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}"));
239+
240+
data.add(new TestData("Creator constructor with ID", new CreatorConstructorIdModel("1234-34567-890", asList(10, 11), "twelve", 13),
241+
getPojoCodecProviderBuilder(CreatorConstructorIdModel.class),
242+
"{'_id': '1234-34567-890', 'integersField': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}"));
243+
234244
data.add(new TestData("Creator no-args constructor", new CreatorNoArgsConstructorModel(40, "one", 42),
235245
getPojoCodecProviderBuilder(CreatorNoArgsConstructorModel.class),
236246
"{'integerField': 40, 'stringField': 'one', 'longField': {$numberLong: '42'}}"));
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.bson.codecs.pojo.entities.conventions;
18+
19+
import org.bson.codecs.pojo.annotations.BsonCreator;
20+
import org.bson.codecs.pojo.annotations.BsonId;
21+
import org.bson.codecs.pojo.annotations.BsonProperty;
22+
23+
import java.util.List;
24+
25+
public class CreatorConstructorIdModel {
26+
private final String id;
27+
private final List<Integer> integersField;
28+
private String stringField;
29+
public long longField;
30+
31+
@BsonCreator
32+
public CreatorConstructorIdModel(final @BsonId String id, @BsonProperty("integersField") final List<Integer> integerField,
33+
@BsonProperty("longField") final long longField) {
34+
this.id = id;
35+
this.integersField = integerField;
36+
this.longField = longField;
37+
}
38+
39+
public CreatorConstructorIdModel(final String id, final List<Integer> integersField, final String stringField, final long longField) {
40+
this.id = id;
41+
this.integersField = integersField;
42+
this.stringField = stringField;
43+
this.longField = longField;
44+
}
45+
46+
public String getId() {
47+
return id;
48+
}
49+
50+
public List<Integer> getIntegersField() {
51+
return integersField;
52+
}
53+
54+
public String getStringField() {
55+
return stringField;
56+
}
57+
58+
public void setStringField(final String stringField) {
59+
this.stringField = stringField;
60+
}
61+
62+
public long getLongField() {
63+
return longField;
64+
}
65+
66+
public void setLongField(final long longField) {
67+
this.longField = longField;
68+
}
69+
70+
@Override
71+
public boolean equals(final Object o) {
72+
if (this == o) {
73+
return true;
74+
}
75+
if (o == null || getClass() != o.getClass()) {
76+
return false;
77+
}
78+
79+
CreatorConstructorIdModel that = (CreatorConstructorIdModel) o;
80+
81+
if (getLongField() != that.getLongField()) {
82+
return false;
83+
}
84+
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) {
85+
return false;
86+
}
87+
if (getIntegersField() != null ? !getIntegersField().equals(that.getIntegersField())
88+
: that.getIntegersField() != null) {
89+
return false;
90+
}
91+
return getStringField() != null ? getStringField().equals(that.getStringField()) : that.getStringField() == null;
92+
}
93+
94+
@Override
95+
public int hashCode() {
96+
int result = getId() != null ? getId().hashCode() : 0;
97+
result = 31 * result + (getIntegersField() != null ? getIntegersField().hashCode() : 0);
98+
result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0);
99+
result = 31 * result + (int) (getLongField() ^ (getLongField() >>> 32));
100+
return result;
101+
}
102+
103+
@Override
104+
public String toString() {
105+
return "CreatorConstructorIdModel{"
106+
+ "id='" + id + '\''
107+
+ ", integersField=" + integersField
108+
+ ", stringField='" + stringField + '\''
109+
+ ", longField=" + longField
110+
+ '}';
111+
}
112+
}

0 commit comments

Comments
 (0)