com.haulmont.cuba.core.app.EntityDiffManager.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.app.EntityDiffManager.java

Source

/*
 * 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.app;

import com.haulmont.bali.datastruct.Pair;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.chile.core.model.Range;
import com.haulmont.chile.core.model.utils.InstanceUtils;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesManagerAPI;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesUtils;
import com.haulmont.cuba.core.entity.CategoryAttribute;
import com.haulmont.cuba.core.entity.EmbeddableEntity;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.EntitySnapshot;
import com.haulmont.cuba.core.entity.diff.*;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.ViewHelper;
import org.apache.commons.lang.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.*;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Diff algorithm for Entities.
 */
@Component("cuba_EntityDiffManager")
public class EntityDiffManager {

    @Inject
    protected EntitySnapshotAPI snapshotAPI;

    @Inject
    protected MetadataTools metadataTools;

    @Inject
    protected ExtendedEntities extendedEntities;

    @Inject
    protected Metadata metadata;

    @Inject
    protected DynamicAttributesManagerAPI dynamicAttributesManagerAPI;

    private static final Logger log = LoggerFactory.getLogger(EntityDiffManager.class);

    public EntityDiff getDifference(@Nullable EntitySnapshot first, EntitySnapshot second) {

        // Sort snapshots by date, first - old, second - new
        long firstTime = 0;
        if (first != null && first.getSnapshotDate() != null)
            firstTime = first.getSnapshotDate().getTime();

        long secondTime = 0;
        if (second != null && second.getSnapshotDate() != null)
            secondTime = second.getSnapshotDate().getTime();

        if (secondTime < firstTime) {
            EntitySnapshot snapshot = first;
            first = second;
            second = snapshot;
        }

        checkNotNull(second, "Diff could not be create for null snapshot");

        // Extract views
        View firstView = first != null ? snapshotAPI.extractView(first) : null;
        View secondView = snapshotAPI.extractView(second);

        // Get view for diff
        View diffView;
        if (firstView != null)
            diffView = ViewHelper.intersectViews(firstView, secondView);
        else
            diffView = secondView;

        // Diff
        return getDifferenceByView(first, second, diffView);
    }

    protected EntityDiff getDifferenceByView(EntitySnapshot first, EntitySnapshot second, View diffView) {
        EntityDiff result = new EntityDiff(diffView);
        result.setBeforeSnapshot(first);
        result.setAfterSnapshot(second);

        if (!diffView.getProperties().isEmpty()) {
            Entity firstEntity = first != null ? snapshotAPI.extractEntity(first) : null;
            Entity secondEntity = snapshotAPI.extractEntity(second);

            result.setBeforeEntity(firstEntity);
            result.setAfterEntity(secondEntity);

            Stack<Object> diffBranch = new Stack<>();
            if (secondEntity != null) {
                diffBranch.push(secondEntity);
            }

            List<EntityPropertyDiff> propertyDiffs = getPropertyDiffs(diffView, firstEntity, secondEntity,
                    diffBranch);
            result.setPropertyDiffs(propertyDiffs);
        }
        return result;
    }

    /**
     * Get diffs for entity properties
     *
     * @param diffView     View
     * @param firstEntity  First entity
     * @param secondEntity Second entity
     * @param diffBranch   Diff branch
     * @return Diff list
     */
    protected List<EntityPropertyDiff> getPropertyDiffs(View diffView, Entity firstEntity, Entity secondEntity,
            Stack<Object> diffBranch) {
        List<EntityPropertyDiff> propertyDiffs = new LinkedList<>();

        MetaClass viewMetaClass = metadata.getSession().getClass(diffView.getEntityClass());
        MetaClass metaClass = extendedEntities.getEffectiveMetaClass(viewMetaClass);

        Collection<MetaPropertyPath> metaProperties = metadataTools.getViewPropertyPaths(diffView, metaClass);

        for (MetaPropertyPath metaPropertyPath : metaProperties) {
            MetaProperty metaProperty = metaPropertyPath.getMetaProperty();

            if (!metadataTools.isNotPersistent(metaProperty) && !metadataTools.isSystem(metaProperty)) {
                ViewProperty viewProperty = diffView.getProperty(metaProperty.getName());

                Object firstValue = firstEntity != null ? getPropertyValue(firstEntity, metaPropertyPath) : null;
                Object secondValue = secondEntity != null ? getPropertyValue(secondEntity, metaPropertyPath) : null;

                EntityPropertyDiff diff = getPropertyDifference(firstValue, secondValue, metaProperty, viewProperty,
                        diffBranch);
                if (diff != null)
                    propertyDiffs.add(diff);
            }
        }

        Collection<CategoryAttribute> categoryAttributes = dynamicAttributesManagerAPI
                .getAttributesForMetaClass(metaClass);
        if (categoryAttributes != null) {
            for (CategoryAttribute categoryAttribute : categoryAttributes) {
                MetaPropertyPath metaPropertyPath = DynamicAttributesUtils.getMetaPropertyPath(metaClass,
                        categoryAttribute);
                MetaProperty metaProperty = metaPropertyPath.getMetaProperty();

                Object firstValue = firstEntity != null ? getPropertyValue(firstEntity, metaPropertyPath) : null;
                Object secondValue = secondEntity != null ? getPropertyValue(secondEntity, metaPropertyPath) : null;

                EntityPropertyDiff diff = getDynamicAttributeDifference(firstValue, secondValue, metaProperty,
                        categoryAttribute);
                if (diff != null)
                    propertyDiffs.add(diff);
            }
        }

        Comparator<EntityPropertyDiff> comparator = Comparator.comparing(EntityPropertyDiff::getName);
        Collections.sort(propertyDiffs, comparator);

        return propertyDiffs;
    }

    /**
     * Return difference between property values
     *
     * @param firstValue   First value
     * @param secondValue  Second value
     * @param metaProperty Meta Property
     * @param viewProperty View property
     * @param diffBranch   Branch with passed diffs
     * @return Diff
     */
    protected EntityPropertyDiff getPropertyDifference(Object firstValue, Object secondValue,
            MetaProperty metaProperty, ViewProperty viewProperty, Stack<Object> diffBranch) {
        EntityPropertyDiff propertyDiff = null;

        Range range = metaProperty.getRange();
        if (range.isDatatype() || range.isEnum()) {
            if (!Objects.equals(firstValue, secondValue)) {
                propertyDiff = new EntityBasicPropertyDiff(firstValue, secondValue, metaProperty);
            }
        } else if (range.getCardinality().isMany()) {
            propertyDiff = getCollectionDiff(firstValue, secondValue, viewProperty, metaProperty, diffBranch);
        } else if (range.isClass()) {
            propertyDiff = getClassDiff(firstValue, secondValue, viewProperty, metaProperty, diffBranch);
        }

        return propertyDiff;
    }

    protected EntityPropertyDiff getClassDiff(@Nullable Object firstValue, @Nullable Object secondValue,
            ViewProperty viewProperty, MetaProperty metaProperty, Stack<Object> diffBranch) {
        EntityPropertyDiff propertyDiff = null;
        if (viewProperty.getView() != null) {
            // check exist value in diff branch
            if (!diffBranch.contains(secondValue)) {

                if (secondValue != null) {
                    // added or modified
                    propertyDiff = generateClassDiffFor(secondValue, firstValue, secondValue, viewProperty,
                            metaProperty, diffBranch);
                } else {
                    if (firstValue != null) {
                        // removed or set null
                        propertyDiff = generateClassDiffFor(firstValue, firstValue, null /*secondValue*/,
                                viewProperty, metaProperty, diffBranch);
                    }
                }
            }
        } else {
            if ((firstValue != null) || (secondValue != null))
                log.debug("Not null values for (null) view ignored, property: " + metaProperty.getName()
                        + "in class" + metaProperty.getDeclaringClass().getCanonicalName());
        }
        return propertyDiff;
    }

    /**
     * Generate class difference for selected not null object
     *
     * @param diffObject   Object
     * @param firstValue   First value
     * @param secondValue  Second value
     * @param viewProperty View property
     * @param metaProperty Meta property
     * @param diffBranch   Diff branch
     * @return Property difference
     */
    protected EntityPropertyDiff generateClassDiffFor(Object diffObject, @Nullable Object firstValue,
            @Nullable Object secondValue, ViewProperty viewProperty, MetaProperty metaProperty,
            Stack<Object> diffBranch) {
        // link
        boolean isLinkChange = !Objects.equals(firstValue, secondValue);
        isLinkChange = !(diffObject instanceof EmbeddableEntity) && isLinkChange;

        EntityClassPropertyDiff classPropertyDiff = new EntityClassPropertyDiff(firstValue, secondValue,
                metaProperty, isLinkChange);

        boolean isInternalChange = false;
        diffBranch.push(diffObject);

        List<EntityPropertyDiff> propertyDiffs = getPropertyDiffs(viewProperty.getView(), (Entity) firstValue,
                (Entity) secondValue, diffBranch);

        diffBranch.pop();

        if (!propertyDiffs.isEmpty()) {
            isInternalChange = true;
            classPropertyDiff.setPropertyDiffs(propertyDiffs);
        }

        if (isInternalChange || isLinkChange)
            return classPropertyDiff;
        else
            return null;
    }

    protected EntityPropertyDiff getCollectionDiff(Object firstValue, Object secondValue, ViewProperty viewProperty,
            MetaProperty metaProperty, Stack<Object> diffBranch) {
        EntityPropertyDiff propertyDiff = null;

        Collection<Entity> addedEntities = new LinkedList<>();
        Collection<Entity> removedEntities = new LinkedList<>();
        Collection<Pair<Entity, Entity>> modifiedEntities = new LinkedList<>();

        // collection
        Collection firstCollection = firstValue == null ? Collections.emptyList() : (Collection) firstValue;
        Collection secondCollection = secondValue == null ? Collections.emptyList() : (Collection) secondValue;

        // added or modified
        for (Object item : secondCollection) {
            Entity secondEntity = (Entity) item;
            Entity firstEntity = getRelatedItem(firstCollection, secondEntity);
            if (firstEntity == null)
                addedEntities.add(secondEntity);
            else
                modifiedEntities.add(new Pair<>(firstEntity, secondEntity));
        }

        // removed
        for (Object item : firstCollection) {
            Entity firstEntity = (Entity) item;
            Entity secondEntity = getRelatedItem(secondCollection, firstEntity);
            if (secondEntity == null)
                removedEntities.add(firstEntity);
        }

        boolean changed = !(addedEntities.isEmpty() && removedEntities.isEmpty() && modifiedEntities.isEmpty());
        if (changed) {
            EntityCollectionPropertyDiff diff = new EntityCollectionPropertyDiff(metaProperty);

            for (Entity entity : addedEntities) {
                EntityPropertyDiff addedDiff = getClassDiff(null, entity, viewProperty, metaProperty, diffBranch);
                if (addedDiff != null) {
                    addedDiff.setName(InstanceUtils.getInstanceName(entity));
                    addedDiff.setItemState(EntityPropertyDiff.ItemState.Added);
                    diff.getAddedEntities().add(addedDiff);
                }
            }
            // check modified
            for (Pair<Entity, Entity> entityPair : modifiedEntities) {
                EntityPropertyDiff modifiedDiff = getClassDiff(entityPair.getFirst(), entityPair.getSecond(),
                        viewProperty, metaProperty, diffBranch);
                if (modifiedDiff != null) {
                    modifiedDiff.setName(InstanceUtils.getInstanceName(entityPair.getSecond()));
                    modifiedDiff.setItemState(EntityPropertyDiff.ItemState.Modified);
                    diff.getModifiedEntities().add(modifiedDiff);
                }
            }
            // check removed
            for (Entity entity : removedEntities) {
                EntityPropertyDiff removedDiff = getClassDiff(entity, null, viewProperty, metaProperty, diffBranch);
                if (removedDiff != null) {
                    removedDiff.setName(InstanceUtils.getInstanceName(entity));
                    removedDiff.setItemState(EntityPropertyDiff.ItemState.Removed);
                    diff.getRemovedEntities().add(removedDiff);
                }
            }

            boolean empty = diff.getAddedEntities().isEmpty() && diff.getModifiedEntities().isEmpty()
                    && diff.getRemovedEntities().isEmpty();
            if (!empty)
                propertyDiff = diff;
        }
        return propertyDiff;
    }

    protected EntityPropertyDiff getDynamicAttributeDifference(Object firstValue, Object secondValue,
            MetaProperty metaProperty, CategoryAttribute categoryAttribute) {
        Range range = metaProperty.getRange();
        if (range.isDatatype() || range.isEnum()) {
            if (!Objects.equals(firstValue, secondValue)) {
                return new EntityBasicPropertyDiff(firstValue, secondValue, metaProperty);
            }
        } else if (range.isClass()) {
            if (BooleanUtils.isTrue(categoryAttribute.getIsCollection())) {
                return getDynamicAttributeCollectionDiff(firstValue, secondValue, metaProperty);
            } else {
                if (!Objects.equals(firstValue, secondValue)) {
                    return new EntityClassPropertyDiff(firstValue, secondValue, metaProperty);
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected EntityPropertyDiff getDynamicAttributeCollectionDiff(Object firstValue, Object secondValue,
            MetaProperty metaProperty) {
        Collection<Entity> firstCollection = Optional.ofNullable((Collection<Entity>) firstValue)
                .orElse(Collections.emptyList());
        Collection<Entity> secondCollection = Optional.ofNullable((Collection<Entity>) secondValue)
                .orElse(Collections.emptyList());
        EntityCollectionPropertyDiff collectionDiff = new EntityCollectionPropertyDiff(metaProperty);
        boolean hasChanges = false;
        for (Entity item : secondCollection) {
            if (!firstCollection.contains(item)) {
                EntityPropertyDiff diff = new EntityClassPropertyDiff(null, item, metaProperty);
                diff.setName(InstanceUtils.getInstanceName(item));
                diff.setItemState(EntityPropertyDiff.ItemState.Added);
                collectionDiff.getAddedEntities().add(diff);
                hasChanges = true;
            }
        }

        for (Entity item : firstCollection) {
            if (!secondCollection.contains(item)) {
                EntityPropertyDiff diff = new EntityClassPropertyDiff(item, null, metaProperty);
                diff.setName(InstanceUtils.getInstanceName(item));
                diff.setItemState(EntityPropertyDiff.ItemState.Removed);
                collectionDiff.getAddedEntities().add(diff);
                hasChanges = true;
            }
        }
        return hasChanges ? collectionDiff : null;
    }

    protected Entity getRelatedItem(Collection collection, Entity entity) {
        for (Object item : collection) {
            Entity itemEntity = (Entity) item;
            if (entity.getId().equals(itemEntity.getId()))
                return itemEntity;
        }
        return null;
    }

    protected Object getPropertyValue(Entity entity, MetaPropertyPath propertyPath) {
        return entity.getValue(propertyPath.toString());
    }
}