package core.framework.impl.db; import core.framework.api.db.Column; import core.framework.api.db.DBEnumValue; import core.framework.api.db.PrimaryKey; import core.framework.api.db.Table; import core.framework.api.util.Exceptions; import core.framework.api.util.Sets; import core.framework.impl.reflect.Fields; import core.framework.impl.validate.type.DataTypeValidator; import core.framework.impl.validate.type.TypeVisitor; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlEnumValue; import java.lang.reflect.Field; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.Set; /** * @author neo */ final class DatabaseClassValidator implements TypeVisitor { private final DataTypeValidator validator; private final Set<String> columns = Sets.newHashSet(); private boolean foundPK; private boolean validateView; DatabaseClassValidator(Class<?> entityClass) { validator = new DataTypeValidator(entityClass); validator.allowedValueClass = this::allowedValueClass; validator.visitor = this; } public void validateEntityClass() { validator.validate(); if (!foundPK) throw Exceptions.error("db entity class must have @PrimaryKey, class={}", validator.type.getTypeName()); } public void validateViewClass() { validateView = true; validator.validate(); } private boolean allowedValueClass(Class<?> valueClass) { return String.class.equals(valueClass) || Integer.class.equals(valueClass) || Boolean.class.equals(valueClass) || Long.class.equals(valueClass) || Double.class.equals(valueClass) || BigDecimal.class.equals(valueClass) || LocalDate.class.equals(valueClass) || LocalDateTime.class.equals(valueClass) || ZonedDateTime.class.equals(valueClass) || valueClass.isEnum(); } @Override public void visitClass(Class<?> objectClass, String path) { if (validateView) { if (objectClass.isAnnotationPresent(Table.class)) throw Exceptions.error("db view class must not have @Table, class={}", objectClass.getCanonicalName()); } else { if (!objectClass.isAnnotationPresent(Table.class)) throw Exceptions.error("db entity class must have @Table, class={}", objectClass.getCanonicalName()); } if (objectClass.isAnnotationPresent(XmlAccessorType.class)) throw Exceptions.error("db entity class must not have jaxb annotation, please separate view class and entity class, class={}", objectClass.getCanonicalName()); } @Override public void visitField(Field field, String parentPath) { Class<?> fieldClass = field.getType(); Column column = field.getDeclaredAnnotation(Column.class); if (column == null) throw Exceptions.error("db entity field must have @Column, field={}", Fields.path(field)); if (columns.contains(column.name())) { throw Exceptions.error("found duplicate column, field={}, column={}", Fields.path(field), column.name()); } else { columns.add(column.name()); } if (fieldClass.isEnum()) { validateEnumClass(fieldClass, field); } PrimaryKey primaryKey = field.getDeclaredAnnotation(PrimaryKey.class); if (primaryKey != null) { foundPK = true; if (primaryKey.autoIncrement() && !(Integer.class.equals(fieldClass) || Long.class.equals(fieldClass))) { throw Exceptions.error("auto increment primary key must be Integer or Long, field={}", Fields.path(field)); } } } private void validateEnumClass(Class<?> enumClass, Field field) { Enum<?>[] constants = (Enum<?>[]) enumClass.getEnumConstants(); Set<String> enumValues = Sets.newHashSet(); for (Enum<?> constant : constants) { try { Field enumField = enumClass.getDeclaredField(constant.name()); DBEnumValue enumValue = enumField.getDeclaredAnnotation(DBEnumValue.class); if (enumValue == null) { throw Exceptions.error("db enum must have @DBEnumValue, field={}, enum={}", Fields.path(field), Fields.path(enumField)); } boolean added = enumValues.add(enumValue.value()); if (!added) { throw Exceptions.error("db enum value must be unique, enum={}, value={}", enumClass.getCanonicalName() + "." + constant, enumValue.value()); } if (enumField.isAnnotationPresent(XmlEnumValue.class)) { throw Exceptions.error("db enum must not have jaxb annotation, please separate view and entity, field={}, enum={}", Fields.path(field), Fields.path(enumField)); } } catch (NoSuchFieldException e) { throw new Error(e); } } } }