org.bson.codecs.pojo.ConventionAnnotationImpl Maven / Gradle / Ivy
/*
* Copyright 2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bson.codecs.pojo;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.pojo.annotations.BsonCreator;
import org.bson.codecs.pojo.annotations.BsonDiscriminator;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.codecs.pojo.annotations.BsonProperty;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static org.bson.codecs.pojo.PojoBuilderHelper.createPropertyModelBuilder;
final class ConventionAnnotationImpl implements Convention {
@Override
public void apply(final ClassModelBuilder classModelBuilder) {
for (final Annotation annotation : classModelBuilder.getAnnotations()) {
processClassAnnotation(classModelBuilder, annotation);
}
for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) {
processPropertyAnnotations(classModelBuilder, propertyModelBuilder);
}
processCreatorAnnotation(classModelBuilder);
cleanPropertyBuilders(classModelBuilder);
}
private void processClassAnnotation(final ClassModelBuilder classModelBuilder, final Annotation annotation) {
if (annotation instanceof BsonDiscriminator) {
BsonDiscriminator discriminator = (BsonDiscriminator) annotation;
String key = discriminator.key();
if (!key.equals("")) {
classModelBuilder.discriminatorKey(key);
}
String name = discriminator.value();
if (!name.equals("")) {
classModelBuilder.discriminator(name);
}
classModelBuilder.enableDiscriminator(true);
}
}
private void processPropertyAnnotations(final ClassModelBuilder classModelBuilder,
final PropertyModelBuilder propertyModelBuilder) {
for (Annotation annotation : propertyModelBuilder.getReadAnnotations()) {
if (annotation instanceof BsonProperty) {
BsonProperty bsonProperty = (BsonProperty) annotation;
if (!"".equals(bsonProperty.value())) {
propertyModelBuilder.readName(bsonProperty.value());
}
propertyModelBuilder.discriminatorEnabled(bsonProperty.useDiscriminator());
} else if (annotation instanceof BsonId) {
classModelBuilder.idPropertyName(propertyModelBuilder.getName());
} else if (annotation instanceof BsonIgnore) {
propertyModelBuilder.readName(null);
}
}
for (Annotation annotation : propertyModelBuilder.getWriteAnnotations()) {
if (annotation instanceof BsonProperty) {
BsonProperty bsonProperty = (BsonProperty) annotation;
if (!"".equals(bsonProperty.value())) {
propertyModelBuilder.writeName(bsonProperty.value());
}
} else if (annotation instanceof BsonIgnore) {
propertyModelBuilder.writeName(null);
}
}
}
@SuppressWarnings("unchecked")
private void processCreatorAnnotation(final ClassModelBuilder classModelBuilder) {
Class clazz = classModelBuilder.getType();
CreatorExecutable creatorExecutable = null;
for (Constructor constructor : clazz.getDeclaredConstructors()) {
if (isPublic(constructor.getModifiers())) {
for (Annotation annotation : constructor.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(BsonCreator.class)) {
creatorExecutable = new CreatorExecutable(clazz, (Constructor) constructor);
break;
}
}
}
}
for (Method method : clazz.getDeclaredMethods()) {
if (isStatic(method.getModifiers())) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(BsonCreator.class)) {
if (creatorExecutable != null) {
throw new CodecConfigurationException("Found multiple constructors / methods annotated with @BsonCreator");
} else if (!clazz.isAssignableFrom(method.getReturnType())) {
throw new CodecConfigurationException(
format("Invalid method annotated with @BsonCreator. Returns '%s', expected %s", method.getReturnType(),
clazz));
}
creatorExecutable = new CreatorExecutable(clazz, method);
break;
}
}
}
}
if (creatorExecutable != null) {
List properties = creatorExecutable.getProperties();
List> parameterTypes = creatorExecutable.getParameterTypes();
if (properties.size() != parameterTypes.size()) {
throw creatorExecutable.getError("All parameters must be annotated with a @Property");
}
for (int i = 0; i < properties.size(); i++) {
BsonProperty bsonProperty = properties.get(i);
Class parameterType = parameterTypes.get(i);
PropertyModelBuilder propertyModelBuilder = classModelBuilder.getProperty(bsonProperty.value());
if (propertyModelBuilder == null) {
addCreatorPropertyToClassModelBuilder(classModelBuilder, bsonProperty.value(), parameterType);
} else if (propertyModelBuilder.getTypeData().getType() != parameterType) {
throw creatorExecutable.getError(format("Invalid Property type for '%s'. Expected %s, found %s.", bsonProperty.value(),
propertyModelBuilder.getTypeData().getType(), parameterType));
}
}
classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl(creatorExecutable));
}
}
private void addCreatorPropertyToClassModelBuilder(final ClassModelBuilder classModelBuilder, final String name,
final Class clazz) {
classModelBuilder.addProperty(createPropertyModelBuilder(new PropertyMetadata(name, classModelBuilder.getType().getSimpleName(),
TypeData.builder(clazz).build())).readName(null).writeName(name));
}
private void cleanPropertyBuilders(final ClassModelBuilder classModelBuilder) {
List propertiesToRemove = new ArrayList();
for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) {
if (!propertyModelBuilder.isReadable() && !propertyModelBuilder.isWritable()) {
propertiesToRemove.add(propertyModelBuilder.getName());
}
}
for (String propertyName : propertiesToRemove) {
classModelBuilder.removeProperty(propertyName);
}
}
}