package ru.hflabs.rcd.service; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.springframework.core.convert.converter.Converter; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import ru.hflabs.rcd.index.IndexedField; import ru.hflabs.rcd.model.annotation.Indexed; import ru.hflabs.rcd.model.definition.ModelDefinition; import ru.hflabs.rcd.model.definition.ModelFieldDefinition; import ru.hflabs.util.core.Holder; import ru.hflabs.util.core.Pair; import ru.hflabs.util.core.collection.ArrayUtil; import ru.hflabs.util.spring.util.ReflectionUtil; import javax.validation.constraints.*; import javax.xml.bind.annotation.XmlTransient; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Date; import java.util.Map; import static ru.hflabs.rcd.accessor.Accessors.injectId; /** * Класс <class>ModelDefinitionFactory</class> реализует фабрику создания описание моделей * * @see ModelDefinition */ public class ModelDefinitionFactory implements IServiceFactory<ModelDefinition, Class<?>> { /** Кеш моделей */ private final Holder<Class<?>, ModelDefinition> modelHolder; /** Сервис получаения индексированных полей */ private Converter<Pair<Class<?>, String>, IndexedField> class2fieldConverter; public ModelDefinitionFactory() { this.modelHolder = new ModelBeanHolder(); } public void setClass2fieldConverter(Converter<Pair<Class<?>, String>, IndexedField> class2fieldConverter) { this.class2fieldConverter = class2fieldConverter; } @Override public ModelDefinition retrieveService(Class<?> key) { return modelHolder.getValue(key); } @Override public void destroyService(Class<?> key, ModelDefinition service) { modelHolder.removeValue(key); } /** * Определяет и возвращает {@link ModelFieldDefinition.FieldType тип поля} * * @param member целевой объект * @return Возвращает тип поля */ private ModelFieldDefinition.FieldType retrieveFieldType(Member member) { Class<?> fieldClass = ClassUtils.resolvePrimitiveIfNecessary(ReflectionUtil.extractFieldType(member)); if (Number.class.isAssignableFrom(fieldClass)) { return ModelFieldDefinition.FieldType.NUMBER; } else if (Boolean.class.isAssignableFrom(fieldClass)) { return ModelFieldDefinition.FieldType.BOOLEAN; } else if (Date.class.isAssignableFrom(fieldClass)) { return ModelFieldDefinition.FieldType.DATE; } else { return ModelFieldDefinition.FieldType.STRING; } } /** * Выполняет поиск указанной аннотации * * @param annotationClass класс аннотации * @param member проверяемый объект * @return Возвращает найденную аннотацию или <code>NULL</code>, если она на задана */ private <T extends Annotation> T findAnnotation(Class<T> annotationClass, Member member) { if (member instanceof Field) { return ((Field) member).getAnnotation(annotationClass); } else if (member instanceof Method) { Method method = (Method) member; T result = method.getAnnotation(annotationClass); if (result == null) { return findAnnotation(annotationClass, ReflectionUtil.findField(member.getDeclaringClass(), ReflectionUtil.extractFieldName(method))); } return result; } else { return null; } } /** * Выполняет обраборку аннотации {@link Size} * * @param bean целевая модель * @param member проверяемый объект * @return Возвращает модифицированную модель */ private ModelFieldDefinition processSizeAnnotation(ModelFieldDefinition bean, Member member) { Size size = findAnnotation(Size.class, member); if (size != null) { bean.setMinLength((long) size.min()); bean.setMaxLength((long) size.max()); } return bean; } /** * Выполняет обраборку аннотации {@link Min} и {@link Max} * * @param bean целевая модель * @param member проверяемый объект * @return Возвращает модифицированную модель */ private ModelFieldDefinition processLengthAnnotation(ModelFieldDefinition bean, Member member) { Min min = findAnnotation(Min.class, member); if (min != null) { bean.setMinLength(min.value()); } Max max = findAnnotation(Max.class, member); if (max != null) { bean.setMaxLength(max.value()); } return bean; } /** * Выполняет обраборку аннотации {@link NotNull} * * @param bean целевая модель * @param member проверяемый объект * @return Возвращает модифицированную модель */ private ModelFieldDefinition processNotNullAnnotation(ModelFieldDefinition bean, Member member) { if (findAnnotation(NotNull.class, member) != null) { bean.setRequired(true); } return bean; } /** * Выполняет обраборку аннотации {@link Pattern} * * @param bean целевая модель * @param member проверяемый объект * @return Возвращает модифицированную модель */ private ModelFieldDefinition processPatternAnnotation(ModelFieldDefinition bean, Member member) { Pattern pattern = findAnnotation(Pattern.class, member); if (pattern != null) { bean.setPattern(pattern.regexp()); } return bean; } /** * Выполняет обраборку аннотации {@link ru.hflabs.rcd.model.annotation.Indexed} * * @param targetClass целевой класс * @param bean целевая модель * @param member проверяемый объект * @return Возвращает модифицированную модель */ private ModelFieldDefinition processIndexedAnnotation(Class<?> targetClass, ModelFieldDefinition bean, Member member) { if (targetClass.isAnnotationPresent(Indexed.class)) { try { // Получаем индексированное поле IndexedField indexedField = class2fieldConverter.convert(Pair.<Class<?>, String>valueOf(targetClass, ReflectionUtil.extractFieldName(member))); // Заполняем дескриптор bean.setSortable(indexedField.isStateEnabled(IndexedField.SORTABLE)); } catch (IllegalArgumentException ex) { // do noting } } return bean; } /** * Выполняем построение модели объекта * * @param targetClass целевой класс * @return Возвращает модель объекта */ private ModelDefinition buildInstance(Class<?> targetClass) { final ModelDefinition result = injectId(new ModelDefinition(), targetClass.getSimpleName()); // Формируем доступные методы final Collection<Member> members = Lists.newLinkedList(); ReflectionUtil.doWithMethods( targetClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { members.add(method); } }, new ReflectionUtils.MethodFilter() { @Override public boolean matches(Method method) { return Modifier.isPublic(method.getModifiers()) && !Modifier.isNative(method.getModifiers()) && !method.isSynthetic() && !method.isAnnotationPresent(XmlTransient.class) && ArrayUtil.isEmpty(method.getParameterTypes()) && (method.getName().startsWith("get") || method.getName().startsWith("is")); } } ); // Для каждого доступного метотода формируем дескриптор Map<String, ModelFieldDefinition> fields = Maps.newLinkedHashMap(); for (Member member : members) { ModelFieldDefinition modelFieldDefinition = new ModelFieldDefinition(); // Type modelFieldDefinition.setType(retrieveFieldType(member)); // Size modelFieldDefinition = processSizeAnnotation(modelFieldDefinition, member); // Length modelFieldDefinition = processLengthAnnotation(modelFieldDefinition, member); // NotNull modelFieldDefinition = processNotNullAnnotation(modelFieldDefinition, member); // Pattern modelFieldDefinition = processPatternAnnotation(modelFieldDefinition, member); // Indexed modelFieldDefinition = processIndexedAnnotation(targetClass, modelFieldDefinition, member); // Сохраняем построенный дескриптор fields.put(ReflectionUtil.extractFieldName(member), modelFieldDefinition); } // Сохраняем найденные поля result.setFields(fields); // Возвращаем построенную модель return result; } /** * Класс <class>ModelBeanHolder</class> * * @author Nazin Alexander */ private class ModelBeanHolder extends Holder<Class<?>, ModelDefinition> { @Override protected ModelDefinition createValue(Class<?> key) { return buildInstance(key); } } }