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.global; import com.google.common.collect.ImmutableList; import com.haulmont.chile.core.annotations.NamePattern; import com.haulmont.chile.core.datatypes.Datatype; import com.haulmont.chile.core.datatypes.Datatypes; import com.haulmont.chile.core.datatypes.impl.EnumClass; import com.haulmont.chile.core.model.*; import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesUtils; import com.haulmont.cuba.core.app.dynamicattributes.PropertyType; import com.haulmont.cuba.core.entity.*; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.entity.annotation.IgnoreUserTimeZone; import com.haulmont.cuba.core.entity.annotation.SystemLevel; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.springframework.stereotype.Component; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; import javax.persistence.*; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.*; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; import static com.haulmont.bali.util.Preconditions.formatExceptionArgs; /** * Utility class to provide common metadata-related functionality. <br> Implemented as Spring bean to allow extension in * application projects. <br> A reference to this class can be obtained either via DI or by {@link * com.haulmont.cuba.core.global.Metadata#getTools()}. */ @Component(MetadataTools.NAME) public class MetadataTools { public static final String NAME = "cuba_MetadataTools"; public static final String PERSISTENT_ANN_NAME = "cuba.persistent"; public static final String PRIMARY_KEY_ANN_NAME = "cuba.primaryKey"; public static final String EMBEDDED_ANN_NAME = "cuba.embedded"; public static final String TEMPORAL_ANN_NAME = "cuba.temporal"; public static final String SYSTEM_ANN_NAME = "cuba.system"; public static final String STORE_ANN_NAME = "cuba.storeName"; public static final List<Class> SYSTEM_INTERFACES = ImmutableList.of(Instance.class, Entity.class, BaseGenericIdEntity.class, Versioned.class, Creatable.class, Updatable.class, SoftDelete.class, HasUuid.class); @Inject protected Metadata metadata; @Inject protected Messages messages; @Inject protected UserSessionSource userSessionSource; @Inject protected DatatypeFormatter datatypeFormatter; @Inject protected PersistentAttributesLoadChecker persistentAttributesLoadChecker; protected volatile Collection<Class> enums; /** * Default constructor used by container at runtime and in server-side integration tests. */ public MetadataTools() { } /** * Formats a value according to the property type. * * @param value object to format * @param property metadata * @return formatted value as string */ public String format(@Nullable Object value, MetaProperty property) { if (value == null) return ""; Objects.requireNonNull(property, "property is null"); Range range = property.getRange(); if (DynamicAttributesUtils.isDynamicAttribute(property)) { CategoryAttribute categoryAttribute = DynamicAttributesUtils.getCategoryAttribute(property); if (categoryAttribute.getDataType().equals(PropertyType.ENUMERATION)) { return LocaleHelper.getEnumLocalizedValue((String) value, categoryAttribute.getEnumerationLocales()); } if (categoryAttribute.getIsCollection() && value instanceof Collection) { return DynamicAttributesUtils.getDynamicAttributeValueAsString(property, value); } } if (range.isDatatype()) { Datatype datatype = range.asDatatype(); if (value instanceof Date && datatype.getJavaClass().equals(Date.class)) { Boolean ignoreUserTimeZone = getMetaAnnotationValue(property, IgnoreUserTimeZone.class); if (!Boolean.TRUE.equals(ignoreUserTimeZone)) { return datatypeFormatter.formatDateTime((Date) value); } } return datatype.format(value, userSessionSource.getLocale()); } else if (range.isEnum()) { return messages.getMessage((Enum) value); } else if (value instanceof Instance) { return ((Instance) value).getInstanceName(); } else { return value.toString(); } } /** * Formats a value according to the value type. * * @param value object to format * @return formatted value as string */ public String format(@Nullable Object value) { if (value == null) { return ""; } else if (value instanceof Instance) { return ((Instance) value).getInstanceName(); } else if (value instanceof EnumClass && value instanceof Enum) { return messages.getMessage((Enum) value, userSessionSource.getLocale()); } else { Datatype datatype = Datatypes.get(value.getClass()); if (datatype != null) { return datatype.format(value, userSessionSource.getLocale()); } return value.toString(); } } /** * @return name of a data store of the given entity or null if the entity is not persistent and no data store is * defined for it */ @Nullable public String getStoreName(MetaClass metaClass) { String storeName = (String) metaClass.getAnnotations().get(STORE_ANN_NAME); if (storeName == null) { return isPersistent(metaClass) ? Stores.MAIN : null; } else return storeName; } /** * @return name of a primary key attribute, or null if the entity has no primary key (e.g. embeddable) */ @Nullable public String getPrimaryKeyName(MetaClass metaClass) { String pkProperty = (String) metaClass.getAnnotations().get(PRIMARY_KEY_ANN_NAME); if (pkProperty != null) { return pkProperty; } else { MetaClass ancestor = metaClass.getAncestor(); while (ancestor != null) { pkProperty = (String) ancestor.getAnnotations().get(PRIMARY_KEY_ANN_NAME); if (pkProperty != null) return pkProperty; ancestor = ancestor.getAncestor(); } } return null; } /** * @return MetaProperty representing a primary key attribute, or null if the entity has no primary key (e.g. * embeddable) */ @Nullable public MetaProperty getPrimaryKeyProperty(MetaClass metaClass) { String primaryKeyName = getPrimaryKeyName(metaClass); return primaryKeyName == null ? null : metaClass.getPropertyNN(primaryKeyName); } /** * @return true if passed MetaClass has a composite primary key */ public boolean hasCompositePrimaryKey(MetaClass metaClass) { MetaProperty primaryKeyProperty = getPrimaryKeyProperty(metaClass); return primaryKeyProperty != null && primaryKeyProperty.getAnnotatedElement().isAnnotationPresent(EmbeddedId.class); } /** * @return true if the first MetaClass is equal or an ancestor of the second. */ public boolean isAssignableFrom(MetaClass metaClass, MetaClass other) { checkNotNullArgument(metaClass); checkNotNullArgument(other); return metaClass.equals(other) || metaClass.getDescendants().contains(other); } /** * Determine whether an object denoted by the given property is merged into persistence context together with the * owning object. This is true if the property is ManyToMany, or if it is OneToMany with certain CascadeType * defined. */ public boolean isCascade(MetaProperty metaProperty) { Objects.requireNonNull(metaProperty, "metaProperty is null"); OneToMany oneToMany = metaProperty.getAnnotatedElement().getAnnotation(OneToMany.class); if (oneToMany != null) { final Collection<CascadeType> cascadeTypes = Arrays.asList(oneToMany.cascade()); if (cascadeTypes.contains(CascadeType.ALL) || cascadeTypes.contains(CascadeType.MERGE)) { return true; } } ManyToMany manyToMany = metaProperty.getAnnotatedElement().getAnnotation(ManyToMany.class); if (manyToMany != null && StringUtils.isBlank(manyToMany.mappedBy())) { return true; } return false; } /** * Determine whether the entity supports <em>Soft Deletion</em>. * * @param entityClass entity class * @return {@code true} if the entity implements {@link SoftDelete} */ public boolean isSoftDeleted(Class entityClass) { return SoftDelete.class.isAssignableFrom(entityClass); } /** * Determine whether the given property is system-level. A property is considered system if it is defined not * in an entity class but in one of its base interfaces: * {@link Entity}, {@link Creatable}, {@link Updatable}, {@link SoftDelete}, {@link Versioned}, {@link HasUuid} */ public boolean isSystem(MetaProperty metaProperty) { Objects.requireNonNull(metaProperty, "metaProperty is null"); return Boolean.TRUE.equals(metaProperty.getAnnotations().get(SYSTEM_ANN_NAME)); } /** * Determine whether all the properties defined by the given property path are persistent. * * @see #isPersistent(com.haulmont.chile.core.model.MetaProperty) */ public boolean isPersistent(MetaPropertyPath metaPropertyPath) { Objects.requireNonNull(metaPropertyPath, "metaPropertyPath is null"); for (MetaProperty metaProperty : metaPropertyPath.getMetaProperties()) { if (!isPersistent(metaProperty)) return false; } return true; } /** * Determine whether the given property is persistent, that is managed by ORM. * <p> * A property is persistent if it is defined in a class registered in persistence.xml and the corresponding * attribute is managed by ORM, i.e. has an annotation like {@code @Column}, {@code @JoinColumn}, etc. * <p> * Note that for properties of non-persistent classes inherited from base classes like {@code BaseUuidEntity} * this method returns true. This is because a meta-property belongs to a class where it is defined, and this method * has no input identifying the real class of interest. * E.g. if you have class {@code Foo extends BaseUuidEntity}, then for the {@code Foo.id} attribute the method * returns true even if the {@code Foo} is defined in metadata.xml and hence not persistent. * <p> * If you need a strict check of whether a certain attribute of an entity is stored in the database via ORM, use * {@link #isPersistent(MetaClass, MetaProperty)}. */ public boolean isPersistent(MetaProperty metaProperty) { Objects.requireNonNull(metaProperty, "metaProperty is null"); return Boolean.TRUE.equals(metaProperty.getAnnotations().get(PERSISTENT_ANN_NAME)); } /** * Determine whether the given property is persistent, that is managed by ORM. */ public boolean isPersistent(MetaClass metaClass, MetaProperty metaProperty) { Objects.requireNonNull(metaClass, "metaClass is null"); Objects.requireNonNull(metaProperty, "metaProperty is null"); return isPersistent(metaClass) && Boolean.TRUE.equals(metaProperty.getAnnotations().get(PERSISTENT_ANN_NAME)); } /** * Determine whether the given property is not persistent. Inverse of {@link #isPersistent(MetaClass, MetaProperty)}. * <p> * For objects and properties not registered in metadata this method returns {@code true}. */ public boolean isNotPersistent(Object object, String property) { Objects.requireNonNull(object, "object is null"); MetaClass metaClass = metadata.getSession().getClass(object.getClass()); if (metaClass == null) return true; MetaProperty metaProperty = metaClass.getProperty(property); return metaProperty == null || !isPersistent(metaClass, metaProperty); } /** * Determine whether the given property is not persistent. Inverse of {@link #isPersistent(MetaProperty)}. */ public boolean isNotPersistent(MetaProperty metaProperty) { return !isPersistent(metaProperty); } /** * Determine whether the given property is not persistent. Inverse of {@link #isPersistent(MetaClass, MetaProperty)}. */ public boolean isNotPersistent(MetaClass metaClass, MetaProperty metaProperty) { return !isPersistent(metaClass, metaProperty); } /** * Determine whether the given property denotes an embedded object. * * @see Embedded */ public boolean isEmbedded(MetaProperty metaProperty) { Objects.requireNonNull(metaProperty, "metaProperty is null"); return Boolean.TRUE.equals(metaProperty.getAnnotations().get(EMBEDDED_ANN_NAME)); } /** * Determine whether the given property is on the owning side of an association. */ public boolean isOwningSide(MetaProperty metaProperty) { checkNotNullArgument(metaProperty, "metaProperty is null"); if (!metaProperty.getRange().isClass()) return false; AnnotatedElement el = metaProperty.getAnnotatedElement(); for (Annotation annotation : el.getAnnotations()) { if (annotation instanceof ManyToOne) return true; if (annotation instanceof OneToMany || annotation instanceof OneToOne) return el.isAnnotationPresent(JoinColumn.class) || el.isAnnotationPresent(JoinTable.class); if (annotation instanceof ManyToMany) return el.isAnnotationPresent(JoinTable.class); } return false; } /** * Determine whether the given entity is marked as {@link SystemLevel}. */ public boolean isSystemLevel(MetaClass metaClass) { Objects.requireNonNull(metaClass, "metaClass is null"); Map<String, Object> metaAnnotationAttributes = getMetaAnnotationAttributes(metaClass.getAnnotations(), SystemLevel.class); return Boolean.TRUE.equals(metaAnnotationAttributes.get("value")); } /** * Determine whether the given property is marked as {@link SystemLevel}. */ public boolean isSystemLevel(MetaProperty metaProperty) { Objects.requireNonNull(metaProperty, "metaProperty is null"); Map<String, Object> metaAnnotationAttributes = getMetaAnnotationAttributes(metaProperty.getAnnotations(), SystemLevel.class); return Boolean.TRUE.equals(metaAnnotationAttributes.get("value")); } public Map<String, Object> getMetaAnnotationAttributes(Map<String, Object> metaAnnotations, Class metaAnnotationClass) { Map map = (Map) metaAnnotations.get(metaAnnotationClass.getName()); return map != null ? map : Collections.emptyMap(); } /** * @return annotation value for specified metaProperty and annotation */ @SuppressWarnings("unchecked") public <T> T getMetaAnnotationValue(MetaProperty metaProperty, Class metaAnnotationClass) { Map<String, Object> metaAnnotationAttributes = getMetaAnnotationAttributes(metaProperty.getAnnotations(), metaAnnotationClass); return (T) metaAnnotationAttributes.get("value"); } /** * Determine whether the given annotation is present in the object's class or in any of its superclasses. * * @param object entity instance * @param property property name * @param annotationClass annotation class */ public boolean isAnnotationPresent(Object object, String property, Class<? extends Annotation> annotationClass) { Objects.requireNonNull(object, "object is null"); return isAnnotationPresent(object.getClass(), property, annotationClass); } /** * Determine whether the given annotation is present in the object's class or in any of its superclasses. * * @param javaClass entity class * @param property property name * @param annotationClass annotation class * @return */ public boolean isAnnotationPresent(Class javaClass, String property, Class<? extends Annotation> annotationClass) { Field field; try { field = javaClass.getDeclaredField(property); return field.isAnnotationPresent(annotationClass); } catch (NoSuchFieldException e) { Class superclass = javaClass.getSuperclass(); while (superclass != null) { try { field = superclass.getDeclaredField(property); return field.isAnnotationPresent(annotationClass); } catch (NoSuchFieldException e1) { superclass = superclass.getSuperclass(); } } throw new RuntimeException("Property not found: " + property); } } /** * Determine whether the given metaclass represents a persistent entity. * <p> * A persistent entity is an entity that is managed by ORM (i.e. registered in a persistence.xml file) * and is not a MappedSuperclass or Embeddable. */ public boolean isPersistent(MetaClass metaClass) { checkNotNullArgument(metaClass, "metaClass is null"); return Boolean.TRUE.equals(metaClass.getAnnotations().get(PERSISTENT_ANN_NAME)) && metaClass.getJavaClass().isAnnotationPresent(javax.persistence.Entity.class); } /** * Determine whether the given class represents a persistent entity. * <p> * A persistent entity is an entity that is managed by ORM (i.e. registered in a persistence.xml file) * and is not a MappedSuperclass or Embeddable. */ public boolean isPersistent(Class aClass) { checkNotNullArgument(aClass, "class is null"); return isPersistent(metadata.getClassNN(aClass)); } /** * Determine whether the given metaclass represents a non-persistent entity. * <p> * A non-persistent entity is not managed by ORM (i.e. registered in a metadata.xml file). * <p> * Note that {@code isNotPersistent()} is not the same as {@code !isPersistent()}, because the latter does not * include MappedSuperclass and Embeddable entities that a still managed by ORM. */ public boolean isNotPersistent(MetaClass metaClass) { return !Boolean.TRUE.equals(metaClass.getAnnotations().get(PERSISTENT_ANN_NAME)); } /** * Determine whether the given class represents a non-persistent entity. * <p> * A non-persistent entity is not managed by ORM (i.e. registered in a metadata.xml file). * <p> * Note that {@code isNotPersistent()} is not the same as {@code !isPersistent()}, because the latter does not * include MappedSuperclass and Embeddable entities that a still managed by ORM. */ public boolean isNotPersistent(Class aClass) { checkNotNullArgument(aClass, "class is null"); return isNotPersistent(metadata.getClassNN(aClass)); } /** * Determine whether the given metaclass is embeddable. */ public boolean isEmbeddable(MetaClass metaClass) { checkNotNullArgument(metaClass, "metaClass is null"); return Boolean.TRUE.equals(metaClass.getAnnotations().get(PERSISTENT_ANN_NAME)) && metaClass.getJavaClass().isAnnotationPresent(javax.persistence.Embeddable.class); } public boolean isCacheable(MetaClass metaClass) { checkNotNullArgument(metaClass, "metaClass is null"); return Boolean.TRUE.equals(metaClass.getAnnotations().get("cacheable")); } /** * Get metaclass that contains metaproperty for passed propertyPath. * Resolves real metaclass for property in consideration of inherited entity classes and extended classes. * * @param propertyPath Property path * @return metaclass */ public MetaClass getPropertyEnclosingMetaClass(MetaPropertyPath propertyPath) { checkNotNullArgument(propertyPath, "Property path should not be null"); MetaProperty[] propertyChain = propertyPath.getMetaProperties(); if (propertyChain.length > 1) { MetaProperty chainProperty = propertyChain[propertyChain.length - 2]; return chainProperty.getRange().asClass(); } else { return propertyPath.getMetaClass(); } } /** * Return a collection of properties included into entity's name pattern (see {@link NamePattern}). * * @param metaClass entity metaclass * @return collection of the name pattern properties */ @Nonnull public Collection<MetaProperty> getNamePatternProperties(MetaClass metaClass) { return getNamePatternProperties(metaClass, false); } /** * Return a collection of properties included into entity's name pattern (see {@link NamePattern}). * * @param metaClass entity metaclass * @param useOriginal if true, and if the given metaclass doesn't define a {@link NamePattern} and if it is an * extended entity, this method tries to find a name pattern in an original entity * @return collection of the name pattern properties */ @Nonnull public Collection<MetaProperty> getNamePatternProperties(MetaClass metaClass, boolean useOriginal) { Collection<MetaProperty> properties = new ArrayList<>(); String pattern = (String) getMetaAnnotationAttributes(metaClass.getAnnotations(), NamePattern.class) .get("value"); if (pattern == null && useOriginal) { MetaClass original = metadata.getExtendedEntities().getOriginalMetaClass(metaClass); if (original != null) { pattern = (String) getMetaAnnotationAttributes(original.getAnnotations(), NamePattern.class) .get("value"); } } if (!StringUtils.isBlank(pattern)) { String value = StringUtils.substringAfter(pattern, "|"); String[] fields = StringUtils.splitPreserveAllTokens(value, ","); for (String field : fields) { String fieldName = StringUtils.trim(field); MetaProperty property = metaClass.getProperty(fieldName); if (property != null) { properties.add(metaClass.getProperty(fieldName)); } else { throw new DevelopmentException( String.format("Property '%s' is not found in %s", field, metaClass.toString()), "NamePattern", pattern); } } } return properties; } /** * @return collection of properties owned by this metaclass and all its ancestors in the form of {@link * MetaPropertyPath}s containing one property each */ public Collection<MetaPropertyPath> getPropertyPaths(MetaClass metaClass) { List<MetaPropertyPath> res = new ArrayList<>(); for (MetaProperty metaProperty : metaClass.getProperties()) { res.add(new MetaPropertyPath(metaClass, metaProperty)); } return res; } /** * Converts a collection of properties to collection of {@link MetaPropertyPath}s containing one property each */ public Collection<MetaPropertyPath> toPropertyPaths(Collection<MetaProperty> properties) { List<MetaPropertyPath> res = new ArrayList<>(); for (MetaProperty metaProperty : properties) { res.add(new MetaPropertyPath(metaProperty.getDomain(), metaProperty)); } return res; } /** * Collects all meta-properties of the given meta-class included to the given view as {@link MetaPropertyPath}s. * * @param view view * @param metaClass meta-class * @return collection of paths */ public Collection<MetaPropertyPath> getViewPropertyPaths(View view, MetaClass metaClass) { List<MetaPropertyPath> propertyPaths = new ArrayList<>(metaClass.getProperties().size()); for (final MetaProperty metaProperty : metaClass.getProperties()) { final MetaPropertyPath metaPropertyPath = new MetaPropertyPath(metaClass, metaProperty); if (viewContainsProperty(view, metaPropertyPath)) { propertyPaths.add(metaPropertyPath); } } return propertyPaths; } /** * Determine whether the view contains a property, traversing a view branch according to the given property path. * * @param view view instance. If null, return false immediately. * @param propertyPath property path defining the property */ public boolean viewContainsProperty(@Nullable View view, MetaPropertyPath propertyPath) { View currentView = view; for (MetaProperty metaProperty : propertyPath.getMetaProperties()) { if (currentView == null) return false; ViewProperty property = currentView.getProperty(metaProperty.getName()); if (property == null) return false; currentView = property.getView(); } return true; } /** * @return collection of all persistent entities */ public Collection<MetaClass> getAllPersistentMetaClasses() { Set<MetaClass> result = new LinkedHashSet<>(); for (MetaClass metaClass : metadata.getSession().getClasses()) { if (isPersistent(metaClass)) { result.add(metaClass); } } return result; } /** * @return collection of all embeddable entities */ public Collection<MetaClass> getAllEmbeddableMetaClasses() { List<MetaClass> result = new ArrayList<>(); for (MetaClass metaClass : metadata.getSession().getClasses()) { if (metaClass.getJavaClass().isAnnotationPresent(javax.persistence.Embeddable.class)) { result.add(metaClass); } } return result; } /** * @return collection of all Java enums used as a type of an entity attribute */ public Collection<Class> getAllEnums() { if (enums == null) { synchronized (this) { enums = new HashSet<>(); for (MetaClass metaClass : metadata.getSession().getClasses()) { for (MetaProperty metaProperty : metaClass.getProperties()) { if (metaProperty.getRange() != null && metaProperty.getRange().isEnum()) { Class c = metaProperty.getRange().asEnumeration().getJavaClass(); enums.add(c); } } } } } return enums; } /** * @param entityClass entity class * @return entity name as defined in {@link javax.persistence.Entity} annotation */ public String getEntityName(Class<?> entityClass) { Annotation annotation = entityClass.getAnnotation(javax.persistence.Entity.class); if (annotation == null) throw new IllegalArgumentException("Class " + entityClass + " is not a persistent entity"); String name = ((javax.persistence.Entity) annotation).name(); if (!StringUtils.isEmpty(name)) return name; else return entityClass.getSimpleName(); } /** * @return table name for the given entity, or null if the entity is Embeddable, MappedSuperclass or non-persistent */ @Nullable public String getDatabaseTable(MetaClass metaClass) { if (isEmbeddable(metaClass) || !isPersistent(metaClass)) return null; Class<?> javaClass = metaClass.getJavaClass(); javax.persistence.Table annotation = javaClass.getAnnotation(javax.persistence.Table.class); if (annotation != null && StringUtils.isNotEmpty(annotation.name())) { return annotation.name(); } else if (metaClass.getAncestor() != null) { return getDatabaseTable(metaClass.getAncestor()); } return null; } @Nullable public String getDatabaseColumn(MetaProperty metaProperty) { if (!isPersistent(metaProperty)) return null; Column column = metaProperty.getAnnotatedElement().getAnnotation(Column.class); if (column != null) { return StringUtils.isEmpty(column.name()) ? metaProperty.getName() : column.name(); } JoinColumn joinColumn = metaProperty.getAnnotatedElement().getAnnotation(JoinColumn.class); if (joinColumn != null) { return StringUtils.isEmpty(joinColumn.name()) ? metaProperty.getName() : joinColumn.name(); } return null; } /** * @return list of related properties defined in {@link com.haulmont.chile.core.annotations.MetaProperty#related()} * or empty list */ public List<String> getRelatedProperties(Class<?> entityClass, String property) { checkNotNullArgument(entityClass, "entityClass is null"); MetaClass metaClass = metadata.getClassNN(entityClass); return getRelatedProperties(metaClass.getPropertyNN(property)); } /** * @return list of related properties defined in {@link com.haulmont.chile.core.annotations.MetaProperty#related()} * or empty list */ public List<String> getRelatedProperties(MetaProperty metaProperty) { checkNotNullArgument(metaProperty, "metaProperty is null"); String relatedProperties = (String) metaProperty.getAnnotations().get("relatedProperties"); List<String> result = Collections.emptyList(); if (relatedProperties != null) { result = Arrays.asList(relatedProperties.split(",")); } return result; } /** * If the given property is a reference to an entity from different data store, returns the name of a persistent * property which stores the identifier of the related entity. * * @param thisStore name of a base data store * @param metaProperty property * @return name of the ID property or null if the given property is not a cross-datastore reference or it does not * satisfy the convention of declaring related properties for such references */ @Nullable public String getCrossDataStoreReferenceIdProperty(String thisStore, MetaProperty metaProperty) { checkNotNullArgument(metaProperty, "metaProperty is null"); if (!metaProperty.getRange().isClass()) return null; String propStore = getStoreName(metaProperty.getRange().asClass()); if (Objects.equals(thisStore, propStore)) return null; List<String> relatedProperties = getRelatedProperties(metaProperty); if (relatedProperties.size() == 1) return relatedProperties.get(0); else return null; } /** * Returns a {@link MetaPropertyPath} which can include the special MetaProperty for a dynamic attribute. * * @param metaClass originating meta-class * @param propertyPath path to the attribute * @return MetaPropertyPath instance */ @Nullable public MetaPropertyPath resolveMetaPropertyPath(MetaClass metaClass, String propertyPath) { checkNotNullArgument(metaClass, "metaClass is null"); MetaPropertyPath metaPropertyPath = metaClass.getPropertyPath(propertyPath); if (metaPropertyPath == null && DynamicAttributesUtils.isDynamicAttribute(propertyPath)) { metaPropertyPath = DynamicAttributesUtils.getMetaPropertyPath(metaClass, propertyPath); } return metaPropertyPath; } /** * Returns a {@link MetaPropertyPath} which can include the special MetaProperty for a dynamic attribute. * Throws an IllegalArgumentException if MetaPropertyPath can't be resolved. * * @param metaClass originating meta-class * @param propertyPath path to the attribute * @return MetaPropertyPath instance */ public MetaPropertyPath resolveMetaPropertyPathNN(MetaClass metaClass, String propertyPath) { MetaPropertyPath metaPropertyPath = resolveMetaPropertyPath(metaClass, propertyPath); checkNotNullArgument(metaPropertyPath, "Could not resolve property path '%s' in '%s'", propertyPath, metaClass); return metaPropertyPath; } /** * Depth-first traversal of the object graph starting from the specified entity instance. * Visits all attributes. * * @param entity entity graph entry point * @param visitor the attribute visitor implementation */ public void traverseAttributes(Entity entity, EntityAttributeVisitor visitor) { checkNotNullArgument(entity, "entity is null"); checkNotNullArgument(visitor, "visitor is null"); internalTraverseAttributes(entity, visitor, new HashSet<>()); } /** * Depth-first traversal of the object graph by the view starting from the specified entity instance. * Visits attributes defined in the view. * * @param view view instance * @param entity entity graph entry point * @param visitor the attribute visitor implementation */ public void traverseAttributesByView(View view, Entity entity, EntityAttributeVisitor visitor) { checkNotNullArgument(view, "view is null"); checkNotNullArgument(entity, "entity is null"); checkNotNullArgument(visitor, "visitor is null"); internalTraverseAttributesByView(view, entity, visitor, new HashMap<>(), false); } /** * Depth-first traversal of the object graph by the view starting from the specified entity instance. * Visits attributes defined in the view. Not loaded attributes by the view aren't visited. * * @param view view instance * @param entity entity graph entry point * @param visitor the attribute visitor implementation */ public void traverseLoadedAttributesByView(View view, Entity entity, EntityAttributeVisitor visitor) { checkNotNullArgument(view, "view is null"); checkNotNullArgument(entity, "entity is null"); checkNotNullArgument(visitor, "visitor is null"); internalTraverseAttributesByView(view, entity, visitor, new HashMap<>(), true); } /** * Create a new instance and make it a shallow copy of the instance given. <br> This method copies attributes * according to the metadata and relies on {@link com.haulmont.chile.core.model.Instance#getMetaClass()} method * which should not return null. * * @param source source instance * @return new instance of the same Java class as source */ public <T extends Instance> T copy(T source) { checkNotNullArgument(source, "source is null"); //noinspection unchecked T dest = createInstance((Class<T>) source.getClass()); copy(source, dest); return dest; } /** * Make a shallow copy of an instance. <br> This method copies attributes according to the metadata and relies on * {@link com.haulmont.chile.core.model.Instance#getMetaClass()} method which should not return null for both * objects. <br> The source and destination instances don't have to be of the same Java class or metaclass. Copying * is performed in the following scenario: get each source property and copy the value to the destination if it * contains a property with the same name and it is not read-only. * * @param source source instance * @param dest destination instance */ public void copy(Instance source, Instance dest) { checkNotNullArgument(source, "source is null"); checkNotNullArgument(dest, "dest is null"); MetaClass sourceMetaClass = metadata.getClassNN(source.getClass()); MetaClass destMetaClass = metadata.getClassNN(dest.getClass()); for (MetaProperty srcProperty : sourceMetaClass.getProperties()) { String name = srcProperty.getName(); MetaProperty dstProperty = destMetaClass.getProperty(name); if (dstProperty != null && !dstProperty.isReadOnly() && persistentAttributesLoadChecker.isLoaded(source, name)) { try { dest.setValue(name, source.getValue(name)); } catch (RuntimeException e) { Throwable cause = ExceptionUtils.getRootCause(e); if (cause == null) cause = e; // ignore exception on copy for not loaded fields if (!(cause instanceof IllegalStateException)) throw e; } } } if (source instanceof BaseGenericIdEntity && dest instanceof BaseGenericIdEntity) { ((BaseGenericIdEntity) dest) .setDynamicAttributes(((BaseGenericIdEntity<?>) source).getDynamicAttributes()); } } public interface EntitiesHolder { Entity create(Class<? extends Entity> entityClass, Object id); Entity find(Object id); void put(Entity entity); } public static class CachingEntitiesHolder implements EntitiesHolder { protected Map<Object, Entity> cache = new HashMap<>(); @Override public Entity create(Class<? extends Entity> entityClass, Object id) { Entity entity = cache.get(id); if (entity == null) { entity = createInstanceWithId(entityClass, id); cache.put(id, entity); } return entity; } @Override public Entity find(Object id) { return cache.get(id); } @Override public void put(Entity entity) { cache.put(entity.getId(), entity); } } /** * Make deep copy of the source entity: all referred entities ant collections will be copied as well */ public <T extends Entity> T deepCopy(T source) { CachingEntitiesHolder entityFinder = new CachingEntitiesHolder(); Entity destination = entityFinder.create(source.getClass(), source.getId()); deepCopy(source, destination, entityFinder); return (T) destination; } /** * Copies all property values from source to destination excluding null values. */ public void deepCopy(Entity source, Entity destination, EntitiesHolder entitiesHolder) { for (MetaProperty srcProperty : source.getMetaClass().getProperties()) { String name = srcProperty.getName(); if (srcProperty.isReadOnly() || !persistentAttributesLoadChecker.isLoaded(source, name)) { continue; } Object value = source.getValue(name); if (value == null) { continue; } if (srcProperty.getRange().isClass()) { Class refClass = srcProperty.getRange().asClass().getJavaClass(); if (!isPersistent(refClass)) { continue; } if (srcProperty.getRange().getCardinality().isMany()) { //noinspection unchecked Collection<Entity> srcCollection = (Collection) value; Collection<Entity> dstCollection = value instanceof List ? new ArrayList<>() : new LinkedHashSet<>(); for (Entity srcRef : srcCollection) { Entity reloadedRef = entitiesHolder.find(srcRef.getId()); if (reloadedRef == null) { reloadedRef = entitiesHolder.create(srcRef.getClass(), srcRef.getId()); deepCopy(srcRef, reloadedRef, entitiesHolder); } dstCollection.add(reloadedRef); } destination.setValue(name, dstCollection); } else { Entity srcRef = (Entity) value; Entity reloadedRef = entitiesHolder.find(srcRef.getId()); if (reloadedRef == null) { reloadedRef = entitiesHolder.create(srcRef.getClass(), srcRef.getId()); deepCopy(srcRef, reloadedRef, entitiesHolder); } destination.setValue(name, reloadedRef); } } else { destination.setValue(name, value); } } if (source instanceof BaseGenericIdEntity && destination instanceof BaseGenericIdEntity) { ((BaseGenericIdEntity) destination) .setDynamicAttributes(((BaseGenericIdEntity<?>) source).getDynamicAttributes()); } } protected void internalTraverseAttributes(Entity entity, EntityAttributeVisitor visitor, HashSet<Object> visited) { if (visited.contains(entity)) return; visited.add(entity); for (MetaProperty property : entity.getMetaClass().getProperties()) { if (visitor.skip(property)) continue; visitor.visit(entity, property); if (property.getRange().isClass()) { if (persistentAttributesLoadChecker.isLoaded(entity, property.getName())) { Object value = entity.getValue(property.getName()); if (value != null) { if (value instanceof Collection) { for (Object item : ((Collection) value)) { internalTraverseAttributes((Entity) item, visitor, visited); } } else { internalTraverseAttributes((Entity) value, visitor, visited); } } } } } } protected void internalTraverseAttributesByView(View view, Entity entity, EntityAttributeVisitor visitor, Map<Entity, Set<View>> visited, boolean checkLoaded) { Set<View> views = visited.get(entity); if (views == null) { views = new HashSet<>(); visited.put(entity, views); } else if (views.contains(view)) { return; } views.add(view); MetaClass metaClass = metadata.getClassNN(entity.getClass()); for (ViewProperty property : view.getProperties()) { MetaProperty metaProperty = metaClass.getPropertyNN(property.getName()); if (visitor.skip(metaProperty)) continue; if (checkLoaded && !persistentAttributesLoadChecker.isLoaded(entity, metaProperty.getName())) continue; View propertyView = property.getView(); visitor.visit(entity, metaProperty); Object value = entity.getValue(property.getName()); if (value != null && propertyView != null) { if (value instanceof Collection) { for (Object item : ((Collection) value)) { if (item instanceof Instance) internalTraverseAttributesByView(propertyView, (Entity) item, visitor, visited, checkLoaded); } } else if (value instanceof Instance) { internalTraverseAttributesByView(propertyView, (Entity) value, visitor, visited, checkLoaded); } } } } protected static <T> T createInstance(Class<T> aClass) { try { return aClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } protected static Entity createInstanceWithId(Class<? extends Entity> entityClass, Object id) { Entity entity = createInstance(entityClass); if (entity instanceof BaseGenericIdEntity) { ((BaseGenericIdEntity) entity).setId(id); } return entity; } /** * DEPRECATED! * Use {@link #isNotPersistent(MetaClass)}. */ @Deprecated public boolean isTransient(MetaClass metaClass) { return isNotPersistent(metaClass); } /** * DEPRECATED! * Use {@link #isNotPersistent(Class)}. */ @Deprecated public boolean isTransient(Class aClass) { return isNotPersistent(aClass); } /** * DEPRECATED! * Use {@link #isNotPersistent(Object, String)}. */ @Deprecated public boolean isTransient(Object object, String property) { return isNotPersistent(object, property); } /** * DEPRECATED! * Use {@link #isNotPersistent(MetaProperty)}. */ @Deprecated public boolean isTransient(MetaProperty metaProperty) { return !isPersistent(metaProperty); } }