Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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 com.haulmont.cuba.core.sys; import com.google.common.base.Joiner; import com.haulmont.bali.util.ReflectionHelper; import com.haulmont.chile.core.annotations.Composition; import com.haulmont.chile.core.annotations.NumberFormat; import com.haulmont.chile.core.datatypes.Datatype; import com.haulmont.chile.core.datatypes.DatatypeRegistry; import com.haulmont.chile.core.datatypes.impl.AdaptiveNumberDatatype; import com.haulmont.chile.core.datatypes.impl.EnumerationImpl; import com.haulmont.chile.core.model.*; import com.haulmont.chile.core.model.impl.*; import com.haulmont.cuba.core.entity.annotation.MetaAnnotation; import com.haulmont.cuba.core.global.MetadataTools; import com.haulmont.cuba.core.global.validation.groups.UiComponentChecks; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ClassUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.validator.constraints.Length; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import javax.annotation.Nullable; import javax.inject.Inject; import javax.persistence.*; import javax.validation.constraints.*; import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.util.*; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; import static org.apache.commons.lang.StringUtils.isBlank; /** * INTERNAL. * Loads meta-model from a set of annotated Java classes. */ @Component(MetaModelLoader.NAME) @Scope("prototype") public class MetaModelLoader { public static final String NAME = "cuba_MetaModelLoader"; protected static final String VALIDATION_MIN = "_min"; protected static final String VALIDATION_MAX = "_max"; protected static final String VALIDATION_NOTNULL_MESSAGE = "_notnull_message"; protected static final String VALIDATION_NOTNULL_UI_COMPONENT = "_notnull_ui_component"; protected DatatypeRegistry datatypes; protected Session session; private static final Logger log = LoggerFactory.getLogger(MetaModelLoader.class); public MetaModelLoader(Session session) { this.session = session; } @Inject public void setDatatypeRegistry(DatatypeRegistry datatypeRegistry) { this.datatypes = datatypeRegistry; } public void loadModel(String rootPackage, List<EntityClassInfo> classInfos) { checkNotNullArgument(rootPackage, "rootPackage is null"); checkNotNullArgument(classInfos, "classInfos is null"); Map<Class<?>, Boolean> classes = new LinkedHashMap<>(); for (EntityClassInfo classInfo : classInfos) { try { classes.put(ReflectionHelper.loadClass(classInfo.name), classInfo.persistent); } catch (ClassNotFoundException e) { log.warn("Class {} not found for model {}", classInfo.name, rootPackage); } } for (Map.Entry<Class<?>, Boolean> entry : classes.entrySet()) { Class<?> aClass = entry.getKey(); if (aClass.getName().startsWith(rootPackage)) { MetaClassImpl metaClass = createClass(aClass, rootPackage); if (metaClass == null) { log.warn("Class {} is not loaded into metadata", aClass.getName()); } } else { log.warn("Class {} is not under root package {} and will not be included to metadata", aClass.getName(), rootPackage); } } for (Map.Entry<Class<?>, Boolean> entry : classes.entrySet()) { Class<?> aClass = entry.getKey(); MetaClassImpl metaClass = (MetaClassImpl) session.getClass(aClass); if (metaClass != null) { onClassLoaded(metaClass, aClass, entry.getValue()); } } List<RangeInitTask> tasks = new ArrayList<>(); for (Map.Entry<Class<?>, Boolean> entry : classes.entrySet()) { Class<?> aClass = entry.getKey(); if (aClass.getName().startsWith(rootPackage)) { MetadataObjectInfo<MetaClass> info = loadClass(rootPackage, aClass, entry.getValue()); if (info != null) { tasks.addAll(info.getTasks()); } else { log.warn("Class {} is not loaded into metadata", aClass.getName()); } } else { log.warn("Class {} is not under root package {} and will not be included to metadata", aClass.getName(), rootPackage); } } for (RangeInitTask task : tasks) { task.execute(); } } @Nullable protected MetadataObjectInfo<MetaClass> loadClass(String packageName, Class<?> javaClass, boolean persistent) { MetaClassImpl metaClass = (MetaClassImpl) session.getClass(javaClass); if (metaClass == null) return null; Collection<RangeInitTask> tasks = new ArrayList<>(); Collection<MetaClass> ancestors = metaClass.getAncestors(); for (MetaClass ancestor : ancestors) { initProperties(ancestor.getJavaClass(), ((MetaClassImpl) ancestor), tasks); } initProperties(javaClass, metaClass, tasks); return new MetadataObjectInfo<>(metaClass, tasks); } @Nullable protected MetaClassImpl createClass(Class<?> javaClass, String packageName) { if (AbstractInstance.class.equals(javaClass) || Object.class.equals(javaClass)) { return null; } MetaClassImpl metaClass = (MetaClassImpl) session.getClass(javaClass); if (metaClass != null) { return metaClass; } else if (packageName == null || javaClass.getName().startsWith(packageName)) { String name = getMetaClassName(javaClass); if (name == null) return null; metaClass = createClassInModel(packageName, name); metaClass.setJavaClass(javaClass); Class<?> ancestor = javaClass.getSuperclass(); if (ancestor != null) { MetaClass ancestorClass = createClass(ancestor, packageName); if (ancestorClass != null) { metaClass.addAncestor(ancestorClass); } } return metaClass; } else { return null; } } protected String getMetaClassName(Class<?> javaClass) { Entity entityAnnotation = javaClass.getAnnotation(Entity.class); MappedSuperclass mappedSuperclassAnnotation = javaClass.getAnnotation(MappedSuperclass.class); com.haulmont.chile.core.annotations.MetaClass metaClassAnnotation = javaClass .getAnnotation(com.haulmont.chile.core.annotations.MetaClass.class); Embeddable embeddableAnnotation = javaClass.getAnnotation(Embeddable.class); if ((entityAnnotation == null && mappedSuperclassAnnotation == null) && (embeddableAnnotation == null) && (metaClassAnnotation == null)) { log.trace( String.format("Class '%s' isn't annotated as metadata entity, ignore it", javaClass.getName())); return null; } String name = null; if (entityAnnotation != null) { name = entityAnnotation.name(); } else if (metaClassAnnotation != null) { name = metaClassAnnotation.name(); } if (StringUtils.isEmpty(name)) { name = javaClass.getSimpleName(); } return name; } protected void onClassLoaded(MetaClass metaClass, Class<?> javaClass, boolean persistent) { if (persistent) { metaClass.getAnnotations().put(MetadataTools.PERSISTENT_ANN_NAME, true); } } protected void initProperties(Class<?> clazz, MetaClassImpl metaClass, Collection<RangeInitTask> tasks) { if (!metaClass.getOwnProperties().isEmpty()) return; // load collection properties after non-collection in order to have all inverse properties loaded up ArrayList<Field> collectionProps = new ArrayList<>(); for (Field field : clazz.getDeclaredFields()) { if (field.isSynthetic()) continue; final String fieldName = field.getName(); if (isMetaPropertyField(field)) { MetaPropertyImpl property = (MetaPropertyImpl) metaClass.getProperty(fieldName); if (property == null) { MetadataObjectInfo<MetaProperty> info; if (isCollection(field) || isMap(field)) { collectionProps.add(field); } else { info = loadProperty(metaClass, field); tasks.addAll(info.getTasks()); MetaProperty metaProperty = info.getObject(); onPropertyLoaded(metaProperty, field); } } else { log.warn("Field " + clazz.getSimpleName() + "." + field.getName() + " is not included in metadata because property " + property + " already exists"); } } } for (Field f : collectionProps) { MetadataObjectInfo<MetaProperty> info = loadCollectionProperty(metaClass, f); tasks.addAll(info.getTasks()); MetaProperty metaProperty = info.getObject(); onPropertyLoaded(metaProperty, f); } for (Method method : clazz.getDeclaredMethods()) { if (method.isSynthetic()) continue; String methodName = method.getName(); if (!methodName.startsWith("get") || method.getReturnType() == void.class) continue; if (isMetaPropertyMethod(method)) { String name = StringUtils.uncapitalize(methodName.substring(3)); MetaPropertyImpl property = (MetaPropertyImpl) metaClass.getProperty(name); if (property == null) { MetadataObjectInfo<MetaProperty> info; if (isCollection(method) || isMap(method)) { throw new UnsupportedOperationException( String.format("Method-based property %s.%s doesn't support collections and maps", clazz.getSimpleName(), method.getName())); } else if (method.getParameterCount() != 0) { throw new UnsupportedOperationException( String.format("Method-based property %s.%s doesn't support arguments", clazz.getSimpleName(), method.getName())); } else { info = loadProperty(metaClass, method, name); tasks.addAll(info.getTasks()); } MetaProperty metaProperty = info.getObject(); onPropertyLoaded(metaProperty, method); } else { log.warn("Method " + clazz.getSimpleName() + "." + method.getName() + " is not included in metadata because property " + property + " already exists"); } } } } protected boolean isMetaPropertyField(Field field) { return field.isAnnotationPresent(Column.class) || field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToMany.class) || field.isAnnotationPresent(ManyToMany.class) || field.isAnnotationPresent(OneToOne.class) || field.isAnnotationPresent(Embedded.class) || field.isAnnotationPresent(EmbeddedId.class) || field.isAnnotationPresent(com.haulmont.chile.core.annotations.MetaProperty.class); } protected boolean isMetaPropertyMethod(Method method) { return method.isAnnotationPresent(com.haulmont.chile.core.annotations.MetaProperty.class); } protected MetadataObjectInfo<MetaProperty> loadProperty(MetaClassImpl metaClass, Field field) { Collection<RangeInitTask> tasks = new ArrayList<>(); MetaPropertyImpl property = new MetaPropertyImpl(metaClass, field.getName()); Range.Cardinality cardinality = getCardinality(field); Map<String, Object> map = new HashMap<>(); map.put("cardinality", cardinality); boolean mandatory = isMandatory(field); map.put("mandatory", mandatory); Datatype datatype = getAdaptiveDatatype(field); map.put("datatype", datatype); String inverseField = getInverseField(field); if (inverseField != null) map.put("inverseField", inverseField); Class<?> type; Class typeOverride = getTypeOverride(field); if (typeOverride != null) type = typeOverride; else type = field.getType(); property.setMandatory(mandatory); property.setReadOnly(!setterExists(field)); property.setAnnotatedElement(field); property.setDeclaringClass(field.getDeclaringClass()); MetadataObjectInfo<Range> info = loadRange(property, type, map); Range range = info.getObject(); if (range != null) { ((AbstractRange) range).setCardinality(cardinality); property.setRange(range); assignPropertyType(field, property, range); assignInverse(property, range, inverseField); } if (info.getObject() != null && info.getObject().isEnum()) { property.setJavaType(info.getObject().asEnumeration().getJavaClass()); } else { property.setJavaType(field.getType()); } tasks.addAll(info.getTasks()); return new MetadataObjectInfo<>(property, tasks); } protected MetadataObjectInfo<MetaProperty> loadProperty(MetaClassImpl metaClass, Method method, String name) { Collection<RangeInitTask> tasks = new ArrayList<>(); MetaPropertyImpl property = new MetaPropertyImpl(metaClass, name); Map<String, Object> map = new HashMap<>(); map.put("cardinality", Range.Cardinality.NONE); map.put("mandatory", false); Datatype datatype = getAdaptiveDatatype(method); map.put("datatype", datatype); Class<?> type; Class typeOverride = getTypeOverride(method); if (typeOverride != null) type = typeOverride; else type = method.getReturnType(); property.setMandatory(false); property.setReadOnly(!setterExists(method)); property.setAnnotatedElement(method); property.setDeclaringClass(method.getDeclaringClass()); property.setJavaType(method.getReturnType()); MetadataObjectInfo<Range> info = loadRange(property, type, map); Range range = info.getObject(); if (range != null) { ((AbstractRange) range).setCardinality(Range.Cardinality.NONE); property.setRange(range); assignPropertyType(method, property, range); } tasks.addAll(info.getTasks()); return new MetadataObjectInfo<>(property, tasks); } protected MetadataObjectInfo<MetaProperty> loadCollectionProperty(MetaClassImpl metaClass, Field field) { Collection<RangeInitTask> tasks = new ArrayList<>(); MetaPropertyImpl property = new MetaPropertyImpl(metaClass, field.getName()); Class type = getFieldType(field); Range.Cardinality cardinality = getCardinality(field); boolean ordered = isOrdered(field); boolean mandatory = isMandatory(field); String inverseField = getInverseField(field); Map<String, Object> map = new HashMap<>(); map.put("cardinality", cardinality); map.put("ordered", ordered); map.put("mandatory", mandatory); if (inverseField != null) map.put("inverseField", inverseField); property.setAnnotatedElement(field); property.setDeclaringClass(field.getDeclaringClass()); property.setJavaType(field.getType()); MetadataObjectInfo<Range> info = loadRange(property, type, map); Range range = info.getObject(); if (range != null) { ((AbstractRange) range).setCardinality(cardinality); ((AbstractRange) range).setOrdered(ordered); property.setRange(range); assignPropertyType(field, property, range); assignInverse(property, range, inverseField); } property.setMandatory(mandatory); tasks.addAll(info.getTasks()); return new MetadataObjectInfo<>(property, tasks); } protected void onPropertyLoaded(MetaProperty metaProperty, Field field) { loadPropertyAnnotations(metaProperty, field); boolean persistentClass = Boolean.TRUE .equals(metaProperty.getDomain().getAnnotations().get(MetadataTools.PERSISTENT_ANN_NAME)); if (isPersistent(field)) { if (persistentClass) { metaProperty.getAnnotations().put(MetadataTools.PERSISTENT_ANN_NAME, true); } if (isPrimaryKey(field)) { metaProperty.getAnnotations().put(MetadataTools.PRIMARY_KEY_ANN_NAME, true); metaProperty.getDomain().getAnnotations().put(MetadataTools.PRIMARY_KEY_ANN_NAME, metaProperty.getName()); } if (isEmbedded(field) && persistentClass) { metaProperty.getAnnotations().put(MetadataTools.EMBEDDED_ANN_NAME, true); } Column column = field.getAnnotation(Column.class); Lob lob = field.getAnnotation(Lob.class); if (column != null && column.length() != 0 && lob == null) { metaProperty.getAnnotations().put("length", column.length()); } } Temporal temporal = field.getAnnotation(Temporal.class); if (temporal != null) { metaProperty.getAnnotations().put(MetadataTools.TEMPORAL_ANN_NAME, temporal.value()); } boolean system = isPrimaryKey(field) || propertyBelongsTo(field, metaProperty, MetadataTools.SYSTEM_INTERFACES); if (system) metaProperty.getAnnotations().put(MetadataTools.SYSTEM_ANN_NAME, true); } private boolean propertyBelongsTo(Field field, MetaProperty metaProperty, List<Class> systemInterfaces) { String getterName = "get" + StringUtils.capitalize(metaProperty.getName()); Class<?> aClass = field.getDeclaringClass(); //noinspection unchecked List<Class> allInterfaces = ClassUtils.getAllInterfaces(aClass); for (Class intf : allInterfaces) { Method[] methods = intf.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(getterName) && method.getParameterTypes().length == 0) { if (systemInterfaces.contains(intf)) return true; } } } return false; } protected Class getFieldTypeAccordingAnnotations(Field field) { OneToOne oneToOneAnnotation = field.getAnnotation(OneToOne.class); OneToMany oneToManyAnnotation = field.getAnnotation(OneToMany.class); ManyToOne manyToOneAnnotation = field.getAnnotation(ManyToOne.class); ManyToMany manyToManyAnnotation = field.getAnnotation(ManyToMany.class); Class result = null; if (oneToOneAnnotation != null) { result = oneToOneAnnotation.targetEntity(); } else if (oneToManyAnnotation != null) { result = oneToManyAnnotation.targetEntity(); } else if (manyToOneAnnotation != null) { result = manyToOneAnnotation.targetEntity(); } else if (manyToManyAnnotation != null) { result = manyToManyAnnotation.targetEntity(); } return result; } protected Class getTypeOverride(AnnotatedElement element) { Temporal temporal = element.getAnnotation(Temporal.class); if (temporal != null && temporal.value().equals(TemporalType.DATE)) return java.sql.Date.class; else if (temporal != null && temporal.value().equals(TemporalType.TIME)) return java.sql.Time.class; else return null; } protected boolean isMandatory(Field field) { OneToMany oneToManyAnnotation = field.getAnnotation(OneToMany.class); ManyToMany manyToManyAnnotation = field.getAnnotation(ManyToMany.class); if (oneToManyAnnotation != null || manyToManyAnnotation != null) { return false; } Column columnAnnotation = field.getAnnotation(Column.class); OneToOne oneToOneAnnotation = field.getAnnotation(OneToOne.class); ManyToOne manyToOneAnnotation = field.getAnnotation(ManyToOne.class); com.haulmont.chile.core.annotations.MetaProperty metaPropertyAnnotation = field .getAnnotation(com.haulmont.chile.core.annotations.MetaProperty.class); boolean superMandatory = (metaPropertyAnnotation != null && metaPropertyAnnotation.mandatory()) || (field.getAnnotation(NotNull.class) != null && isDefinedForDefaultValidationGroup(field.getAnnotation(NotNull.class))); // @NotNull without groups return (columnAnnotation != null && !columnAnnotation.nullable()) || (oneToOneAnnotation != null && !oneToOneAnnotation.optional()) || (manyToOneAnnotation != null && !manyToOneAnnotation.optional()) || superMandatory; } protected Range.Cardinality getCardinality(Field field) { if (field.isAnnotationPresent(Column.class)) { return Range.Cardinality.NONE; } else if (field.isAnnotationPresent(OneToOne.class)) { return Range.Cardinality.ONE_TO_ONE; } else if (field.isAnnotationPresent(OneToMany.class)) { return Range.Cardinality.ONE_TO_MANY; } else if (field.isAnnotationPresent(ManyToOne.class)) { return Range.Cardinality.MANY_TO_ONE; } else if (field.isAnnotationPresent(ManyToMany.class)) { return Range.Cardinality.MANY_TO_MANY; } else if (field.isAnnotationPresent(Embedded.class)) { return Range.Cardinality.ONE_TO_ONE; } else { Class<?> type = field.getType(); if (Collection.class.isAssignableFrom(type)) { return Range.Cardinality.ONE_TO_MANY; } else if (type.isPrimitive() || type.equals(String.class) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || UUID.class.isAssignableFrom(type)) { return Range.Cardinality.NONE; } else return Range.Cardinality.MANY_TO_ONE; } } protected String getInverseField(Field field) { OneToMany oneToManyAnnotation = field.getAnnotation(OneToMany.class); if (oneToManyAnnotation != null) return isBlank(oneToManyAnnotation.mappedBy()) ? null : oneToManyAnnotation.mappedBy(); ManyToMany manyToManyAnnotation = field.getAnnotation(ManyToMany.class); if (manyToManyAnnotation != null) return isBlank(manyToManyAnnotation.mappedBy()) ? null : manyToManyAnnotation.mappedBy(); OneToOne oneToOneAnnotation = field.getAnnotation(OneToOne.class); if (oneToOneAnnotation != null) return isBlank(oneToOneAnnotation.mappedBy()) ? null : oneToOneAnnotation.mappedBy(); return null; } protected boolean isPrimaryKey(Field field) { return field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class); } protected boolean isEmbedded(Field field) { return field.isAnnotationPresent(Embedded.class) || field.isAnnotationPresent(EmbeddedId.class); } protected boolean isPersistent(Field field) { return field.isAnnotationPresent(Column.class) || field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToMany.class) || field.isAnnotationPresent(ManyToMany.class) || field.isAnnotationPresent(OneToOne.class) || field.isAnnotationPresent(Embedded.class) || field.isAnnotationPresent(EmbeddedId.class); } protected MetaClassImpl createClassInModel(String modelName, String className) { MetaModel model = session.getModel(modelName); if (model == null) { model = new MetaModelImpl(session, modelName); } return new MetaClassImpl(model, className); } protected boolean isCollection(Field field) { final Class<?> type = field.getType(); return Collection.class.isAssignableFrom(type); } protected boolean isMap(Field field) { final Class<?> type = field.getType(); return Map.class.isAssignableFrom(type); } protected boolean isMap(Method method) { final Class<?> type = method.getReturnType(); return Map.class.isAssignableFrom(type); } protected boolean isCollection(Method method) { final Class<?> type = method.getReturnType(); return Collection.class.isAssignableFrom(type); } protected void onPropertyLoaded(MetaProperty metaProperty, Method method) { loadPropertyAnnotations(metaProperty, method); } protected void loadPropertyAnnotations(MetaProperty metaProperty, AnnotatedElement annotatedElement) { for (Annotation annotation : annotatedElement.getAnnotations()) { MetaAnnotation metaAnnotation = AnnotationUtils.findAnnotation(annotation.getClass(), MetaAnnotation.class); if (metaAnnotation != null) { Map<String, Object> attributes = new LinkedHashMap<>( AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation)); metaProperty.getAnnotations().put(annotation.annotationType().getName(), attributes); } } com.haulmont.chile.core.annotations.MetaProperty metaPropertyAnnotation = annotatedElement .getAnnotation(com.haulmont.chile.core.annotations.MetaProperty.class); if (metaPropertyAnnotation != null) { String[] related = metaPropertyAnnotation.related(); if (!(related.length == 1 && related[0].equals(""))) { metaProperty.getAnnotations().put("relatedProperties", Joiner.on(',').join(related)); } } loadBeanValidationAnnotations(metaProperty, annotatedElement); } protected void loadBeanValidationAnnotations(MetaProperty metaProperty, AnnotatedElement annotatedElement) { NotNull notNull = annotatedElement.getAnnotation(NotNull.class); if (notNull != null) { if (isDefinedForDefaultValidationGroup(notNull)) { metaProperty.getAnnotations().put(NotNull.class.getName() + VALIDATION_NOTNULL_MESSAGE, notNull.message()); } if (isDefinedForValidationGroup(notNull, UiComponentChecks.class, true)) { metaProperty.getAnnotations().put(NotNull.class.getName() + VALIDATION_NOTNULL_MESSAGE, notNull.message()); metaProperty.getAnnotations().put(NotNull.class.getName() + VALIDATION_NOTNULL_UI_COMPONENT, true); } } Size size = annotatedElement.getAnnotation(Size.class); if (size != null && isDefinedForDefaultValidationGroup(size)) { metaProperty.getAnnotations().put(Size.class.getName() + VALIDATION_MIN, size.min()); metaProperty.getAnnotations().put(Size.class.getName() + VALIDATION_MAX, size.max()); } Length length = annotatedElement.getAnnotation(Length.class); if (length != null && isDefinedForDefaultValidationGroup(length)) { metaProperty.getAnnotations().put(Length.class.getName() + VALIDATION_MIN, length.min()); metaProperty.getAnnotations().put(Length.class.getName() + VALIDATION_MAX, length.max()); } Min min = annotatedElement.getAnnotation(Min.class); if (min != null && isDefinedForDefaultValidationGroup(min)) { metaProperty.getAnnotations().put(Min.class.getName(), min.value()); } Max max = annotatedElement.getAnnotation(Max.class); if (max != null && isDefinedForDefaultValidationGroup(max)) { metaProperty.getAnnotations().put(Max.class.getName(), max.value()); } DecimalMin decimalMin = annotatedElement.getAnnotation(DecimalMin.class); if (decimalMin != null && isDefinedForDefaultValidationGroup(decimalMin)) { metaProperty.getAnnotations().put(DecimalMin.class.getName(), decimalMin.value()); } DecimalMax decimalMax = annotatedElement.getAnnotation(DecimalMax.class); if (decimalMax != null && isDefinedForDefaultValidationGroup(decimalMax)) { metaProperty.getAnnotations().put(DecimalMax.class.getName(), decimalMax.value()); } Past past = annotatedElement.getAnnotation(Past.class); if (past != null && isDefinedForDefaultValidationGroup(past)) { metaProperty.getAnnotations().put(Past.class.getName(), true); } Future future = annotatedElement.getAnnotation(Future.class); if (future != null && isDefinedForDefaultValidationGroup(future)) { metaProperty.getAnnotations().put(Future.class.getName(), true); } } protected boolean isDefinedForDefaultValidationGroup(Annotation annotation) { return isDefinedForValidationGroup(annotation, javax.validation.groups.Default.class, true); } protected boolean isDefinedForValidationGroup(Annotation annotation, Class groupClass, boolean inheritDefault) { try { Method groupsMethod = annotation.getClass().getMethod("groups"); Class<?>[] groups = (Class<?>[]) groupsMethod.invoke(annotation); if (inheritDefault && groups.length == 0) { return true; } return ArrayUtils.contains(groups, groupClass); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException("Unable to use annotation metadata " + annotation); } } @Nullable protected Datatype getAdaptiveDatatype(AnnotatedElement annotatedElement) { com.haulmont.chile.core.annotations.MetaProperty annotation = annotatedElement .getAnnotation(com.haulmont.chile.core.annotations.MetaProperty.class); return annotation != null && !annotation.datatype().equals("") ? datatypes.get(annotation.datatype()) : null; } protected boolean setterExists(Field field) { String name = "set" + StringUtils.capitalize(field.getName()); Method[] methods = field.getDeclaringClass().getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(name)) return true; } return false; } protected boolean setterExists(Method getter) { if (getter.getName().startsWith("get")) { String setterName = "set" + getter.getName().substring(3); Method[] methods = getter.getDeclaringClass().getDeclaredMethods(); for (Method method : methods) { if (setterName.equals(method.getName())) { return true; } } } return false; } protected void assignPropertyType(AnnotatedElement field, MetaProperty property, Range range) { if (range.isClass()) { Composition composition = field.getAnnotation(Composition.class); if (composition != null) { ((MetaPropertyImpl) property).setType(MetaProperty.Type.COMPOSITION); } else { ((MetaPropertyImpl) property).setType(MetaProperty.Type.ASSOCIATION); } } else if (range.isDatatype()) { ((MetaPropertyImpl) property).setType(MetaProperty.Type.DATATYPE); } else if (range.isEnum()) { ((MetaPropertyImpl) property).setType(MetaProperty.Type.ENUM); } else { throw new UnsupportedOperationException(); } } protected MetadataObjectInfo<Range> loadRange(MetaProperty metaProperty, Class<?> type, Map<String, Object> map) { Datatype datatype = (Datatype) map.get("datatype"); if (datatype != null) { // A datatype is assigned explicitly return new MetadataObjectInfo<>(new DatatypeRange(datatype)); } datatype = getAdaptiveDatatype(metaProperty, type); if (datatype == null) { datatype = datatypes.get(type); } if (datatype != null) { MetaClass metaClass = metaProperty.getDomain(); Class javaClass = metaClass.getJavaClass(); try { String name = "get" + StringUtils.capitalize(metaProperty.getName()); Method method = javaClass.getMethod(name); Class<Enum> returnType = (Class<Enum>) method.getReturnType(); if (returnType.isEnum()) { return new MetadataObjectInfo<>(new EnumerationRange(new EnumerationImpl<>(returnType))); } } catch (NoSuchMethodException e) { // ignore } return new MetadataObjectInfo<>(new DatatypeRange(datatype)); } else if (type.isEnum()) { return new MetadataObjectInfo<>(new EnumerationRange(new EnumerationImpl(type))); } else { return new MetadataObjectInfo<>(null, Collections.singletonList(new RangeInitTask(metaProperty, type, map))); } } @Nullable protected Datatype getAdaptiveDatatype(MetaProperty metaProperty, Class<?> type) { NumberFormat numberFormat = metaProperty.getAnnotatedElement().getAnnotation(NumberFormat.class); if (numberFormat != null) { if (Number.class.isAssignableFrom(type)) { return new AdaptiveNumberDatatype(type, numberFormat); } else { log.warn("NumberFormat annotation is ignored because " + metaProperty + " is not a Number"); } } return null; } protected Class getFieldType(Field field) { Type genericType = field.getGenericType(); Class type; if (genericType instanceof ParameterizedType) { Type[] types = ((ParameterizedType) genericType).getActualTypeArguments(); if (Map.class.isAssignableFrom(field.getType())) type = (Class<?>) types[1]; else type = (Class<?>) types[0]; } else { type = getFieldTypeAccordingAnnotations(field); } if (type == null) throw new IllegalArgumentException("Field " + field + " must either be of parametrized type or have a JPA annotation declaring a targetEntity"); return type; } protected void assignInverse(MetaPropertyImpl property, Range range, String inverseField) { if (inverseField == null) return; if (!range.isClass()) throw new IllegalArgumentException("Range of class type expected"); MetaClass metaClass = range.asClass(); MetaProperty inverseProp = metaClass.getProperty(inverseField); if (inverseProp == null) throw new RuntimeException( String.format("Unable to assign inverse property '%s' for '%s'", inverseField, property)); property.setInverse(inverseProp); } protected boolean isOrdered(Field field) { Class<?> type = field.getType(); return List.class.isAssignableFrom(type) || LinkedHashSet.class.isAssignableFrom(type); } protected class RangeInitTask { private MetaProperty metaProperty; private Class rangeClass; private Map<String, Object> map; public RangeInitTask(MetaProperty metaProperty, Class rangeClass, Map<String, Object> map) { this.metaProperty = metaProperty; this.rangeClass = rangeClass; this.map = map; } public String getWarning() { return String.format("Range for property '%s' wasn't initialized (range class '%s')", metaProperty.getName(), rangeClass.getName()); } public void execute() { MetaClass rangeClass = session.getClass(this.rangeClass); if (rangeClass == null) { throw new IllegalStateException(String.format("Can't find range class '%s' for property '%s.%s'", this.rangeClass.getName(), metaProperty.getDomain(), metaProperty.getName())); } else { ClassRange range = new ClassRange(rangeClass); Range.Cardinality cardinality = (Range.Cardinality) map.get("cardinality"); range.setCardinality(cardinality); if (Range.Cardinality.ONE_TO_MANY.equals(cardinality) || Range.Cardinality.MANY_TO_MANY.equals(cardinality)) { range.setOrdered((Boolean) map.get("ordered")); } Boolean mandatory = (Boolean) map.get("mandatory"); if (mandatory != null) { ((MetaPropertyImpl) metaProperty).setMandatory(mandatory); } ((MetaPropertyImpl) metaProperty).setRange(range); assignPropertyType(metaProperty.getAnnotatedElement(), metaProperty, range); assignInverse((MetaPropertyImpl) metaProperty, range, (String) map.get("inverseField")); } } } public static class MetadataObjectInfo<T> { private T object; private Collection<RangeInitTask> tasks; public MetadataObjectInfo(T object) { this.object = object; this.tasks = Collections.emptyList(); } public MetadataObjectInfo(T object, Collection<? extends RangeInitTask> tasks) { this.object = object; this.tasks = (Collection<RangeInitTask>) tasks; } public T getObject() { return object; } public Collection<RangeInitTask> getTasks() { return tasks; } public void setTasks(Collection<RangeInitTask> tasks) { this.tasks = tasks; } } }