de.iteratec.iteraplan.persistence.elasticeam.model.diff.EMFModelWriter.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.persistence.elasticeam.model.diff.EMFModelWriter.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.persistence.elasticeam.model.diff;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.compare.diff.metamodel.DiffElement;
import org.eclipse.emf.compare.diff.metamodel.DiffModel;
import org.eclipse.emf.compare.diff.service.DiffService;
import org.eclipse.emf.compare.match.metamodel.MatchModel;
import org.eclipse.emf.compare.match.service.MatchService;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.google.common.collect.BiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import de.iteratec.iteraplan.businesslogic.exchange.elasticeam.IteraplanMapping;
import de.iteratec.iteraplan.businesslogic.service.BuildingBlockService;
import de.iteratec.iteraplan.common.DefaultSpringApplicationContext;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.elasticeam.exception.ModelException;
import de.iteratec.iteraplan.elasticeam.metamodel.Metamodel;
import de.iteratec.iteraplan.elasticeam.metamodel.emf.EObjectConverter;
import de.iteratec.iteraplan.elasticeam.metamodel.emf.EPackageConverter;
import de.iteratec.iteraplan.elasticeam.metamodel.emf.Mapping;
import de.iteratec.iteraplan.elasticeam.model.Model;
import de.iteratec.iteraplan.elasticeam.model.UniversalModelExpression;
import de.iteratec.iteraplan.elasticeam.model.diff.AbstractModelElementChange;
import de.iteratec.iteraplan.elasticeam.model.diff.AbstractModelElementChange.TypeOfModelElementChange;
import de.iteratec.iteraplan.elasticeam.model.diff.ModelElementChangeFactory;
import de.iteratec.iteraplan.elasticeam.model.diff.ModelWriter;
import de.iteratec.iteraplan.model.AbstractHierarchicalEntity;
import de.iteratec.iteraplan.model.BuildingBlock;
import de.iteratec.iteraplan.model.interfaces.HierarchicalEntity;

/**
 * Calculate classified {@link AbstractModelElementChange}s describing the differences between two {@link Model}s
 * and can apply these changes to the base model.
 */
public class EMFModelWriter extends ModelWriter {

    private static final Logger LOGGER = Logger.getIteraplanLogger(EMFModelWriter.class);

    private IteraplanMapping mapping;
    private BiMap<Object, UniversalModelExpression> instanceMapping;
    private Map<TypeOfModelElementChange, List<AbstractModelElementChange>> changes;
    private Session session;
    private Transaction transaction;

    public EMFModelWriter(IteraplanMapping mapping, Model model,
            BiMap<Object, UniversalModelExpression> instanceMapping, Model modified) {
        super(model, modified, mapping.getMetamodel());
        this.mapping = mapping;
        this.instanceMapping = instanceMapping;
        compareAndClassifyChanges();
    }

    public EMFModelWriter(Model base, Model modified, Metamodel metamodel) {
        super(base, modified, metamodel);
        compareAndClassifyChanges();
    }

    /**{@inheritDoc}**/
    @Override
    protected final void compareAndClassifyChanges() {
        changes = initChangeMap();
        List<DiffElement> differences = getDifferences(getBaseModel(), getModifiedModel(), getMetamodel(),
                eContainerClass());
        for (DiffElement diff : differences) {
            LOGGER.info("Creating AbstractModelElementChange for difference \"{0}\"...", diff);
            AbstractModelElementChange change = null;
            if (instanceMapping == null) {
                change = ModelElementChangeFactory.INSTANCE.createModelElementChange(diff, getBaseModel(),
                        getModifiedModel(), getMetamodel());
            } else {
                change = EMFModelElementChangeFactory.INSTANCE.createModelElementChange(diff, mapping,
                        getBaseModel(), instanceMapping, getModifiedModel());
            }
            LOGGER.info("Change \"{0}\" created and classified as \"{1}\"", change,
                    change.getTypeOfModelDifference());

            changes.get(change.getTypeOfModelDifference()).add(change);
            LOGGER.debug(diff.getKind() + "(" + diff.getClass().getSimpleName() + ") => "
                    + change.getTypeOfModelDifference().name() + "(" + change.getClass().getSimpleName() + ")");
        }
    }

    protected static List<DiffElement> getDifferences(Model base, Model modified, Metamodel metamodel,
            EClass eClass) {
        LOGGER.info("Calculating model differences...");
        Mapping<Metamodel> metamodelMapping = EPackageConverter.deriveMapping(metamodel, false);
        EObject baseContainer = getEContainer(eClass, base, metamodelMapping, "model");
        EObject modifiedContainer = getEContainer(eClass, modified, metamodelMapping, "model");
        Map<String, Object> options = Maps.newHashMap();
        try {
            MatchModel matchModel = MatchService.doContentMatch(modifiedContainer, baseContainer, options);
            DiffModel diffModel = DiffService.doDiff(matchModel);
            List<DiffElement> differences = diffModel.getDifferences();
            LOGGER.info("{0} differences found.", Integer.valueOf(differences.size()));
            return differences;
        } catch (InterruptedException e) {
            LOGGER.error(e);
            throw new ModelException(ModelException.GENERAL_ERROR,
                    "Could not compare resources " + baseContainer + " and " + modifiedContainer);
        }
    }

    protected static EObject getEContainer(EClass eContainerClass, Model model, Mapping<Metamodel> metamodelMapping,
            String resource) {
        metamodelMapping.getEPackage().getEClassifiers().add(eContainerClass);
        EObject eContainer = eContainerClass.getEPackage().getEFactoryInstance().create(eContainerClass);
        eContainer.eSet(eContainerClass.getEStructuralFeature("id"), resource);
        eContainer.eSet(eContainerClass.getEStructuralFeature("contains"),
                EObjectConverter.export(metamodelMapping, model));
        metamodelMapping.getEPackage().getEClassifiers().remove(eContainerClass);
        return eContainer;
    }

    private EClass eContainerClass() {
        EReference containmentReference = EcoreFactory.eINSTANCE.createEReference();
        containmentReference.setName("contains");
        containmentReference.setUpperBound(EStructuralFeature.UNBOUNDED_MULTIPLICITY);
        containmentReference.setUnique(true);
        containmentReference.setEType(EcoreFactory.eINSTANCE.createEObject().eClass());
        containmentReference.setContainment(true);

        EAttribute id = EcoreFactory.eINSTANCE.createEAttribute();
        id.setName("id");
        id.setID(true);
        id.setLowerBound(1);
        id.setUpperBound(1);
        id.setEType(EcorePackage.eINSTANCE.getEString());

        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Container");

        eClass.getEStructuralFeatures().add(id);
        eClass.getEStructuralFeatures().add(containmentReference);

        return eClass;
    }

    /**{@inheritDoc}**/
    @Override
    public Map<TypeOfModelElementChange, List<AbstractModelElementChange>> getChanges() {
        return changes;
    }

    /**{@inheritDoc}**/
    @Override
    protected void validateCompatibility() {
        //TODO implement more specific compatibility checks;
    }

    /**{@inheritDoc}**/
    @Override
    public Map<TypeOfModelElementChange, List<AbstractModelElementChange>> applyChanges(
            Set<TypeOfModelElementChange> ignoredTypes, Set<AbstractModelElementChange> ignoredChanges) {
        beginTransation();
        Map<TypeOfModelElementChange, List<AbstractModelElementChange>> applied = super.applyChanges(ignoredTypes,
                ignoredChanges);

        LOGGER.info("Persisting changes-related entities...");
        for (TypeOfModelElementChange type : TypeOfModelElementChange.ALL) {
            LOGGER.info("Handling changes of type \"{0}\"...", type);
            for (AbstractModelElementChange change : applied.get(type)) {
                LOGGER.info("Change \"{0}\"...", change);
                Object entity = null;
                if (change instanceof EMFInstanceDeletion) {
                    entity = getEntityToDelete(change);
                    if (entity != null) {
                        session.delete(entity);
                        LOGGER.info("Entity \"{0}\" deleted.", entity);
                    }
                } else if (!(change instanceof TechnicalEMFModelElementChange)) {
                    entity = getEntityToPersist(change);
                    if (entity != null) {
                        session.saveOrUpdate(entity);
                        LOGGER.info("Entity \"{0}\" persisted.", entity);
                    }
                }
            }
        }
        setDefaultParents(applied.get(TypeOfModelElementChange.ADD_INSTANCE));
        // Commit automatically done when returning to the frontend. Not necessary here, then?
        //    commit();
        return applied;
    }

    private Object getEntityToDelete(AbstractModelElementChange change) {
        Object toPersist;
        LOGGER.debug("Getting entity to delete...");
        EMFInstanceDeletion deletion = (EMFInstanceDeletion) change;
        toPersist = instanceMapping.inverse().get(deletion.getDeletedModelExpression());
        LOGGER.debug("Entity to delete: {0}", toPersist);
        return toPersist;
    }

    private Object getEntityToPersist(AbstractModelElementChange change) {
        Object toPersist = null;

        LOGGER.debug("Getting entity to persist...");
        if (change instanceof EMFInstanceExpressionCreation) {
            EMFInstanceExpressionCreation newInstance = (EMFInstanceExpressionCreation) change;
            toPersist = instanceMapping.inverse().get(newInstance.getNewCreatedModelExpression());
            LOGGER.debug("Entity to persist: {0}", toPersist);
        } else if (change instanceof EMFLinkExpressionCreation) {
            EMFLinkExpressionCreation newRelationshipInstance = (EMFLinkExpressionCreation) change;
            toPersist = instanceMapping.inverse().get(newRelationshipInstance.getNewCreatedModelExpression());
            LOGGER.debug("Entity to persist: {0}", toPersist);
        } else if (change instanceof EMFPropertyValueChange) {
            EMFPropertyValueChange valChange = (EMFPropertyValueChange) change;
            toPersist = instanceMapping.inverse().get(valChange.getModelExpressionInCurrentModel());
            LOGGER.debug("Entity to persist: {0}", toPersist);
        } else if (change instanceof EMFLinkChange) {
            EMFLinkChange linkChange = (EMFLinkChange) change;
            toPersist = instanceMapping.inverse().get(linkChange.getSource());
            LOGGER.debug("Entity to persist: {0}", toPersist);
        } else {
            LOGGER.error("Unhandled " + TypeOfModelElementChange.class.getSimpleName() + " detected " + change + "("
                    + change.getTypeOfModelDifference().name() + ")");
        }
        return toPersist;
    }

    /**
     * In case of persisting changes to the iteraplan db, a new transaction should be created before applying any changes
     */
    private void beginTransation() {
        if (session == null && transaction == null) {
            //TODO remove context.getBean(...)
            SessionFactory sessionFactory = (SessionFactory) DefaultSpringApplicationContext
                    .getSpringApplicationContext().getBean("sessionFactory");
            this.session = sessionFactory.getCurrentSession();
            session.setFlushMode(FlushMode.COMMIT);
            if (!session.isOpen()) {
                session = sessionFactory.openSession();
            }
            this.transaction = this.session.beginTransaction();
        } else {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR,
                    "Cannot begin new transaction while there is another active one alive");
        }

    }

    //  /**
    //   * After changes have been applied, changes need to be written to iteraplan db
    //   */
    //  private void commit() {
    //    //TODO make sure transaction won't be committed before this method (currently AttributeValueService.saveOrUpdate(attributeValue) actually commits transaction
    //    if (session != null && (session.isOpen() || session.isDirty())) {
    //      session.flush();
    //    }
    //    if (transaction != null && !transaction.wasCommitted()) {
    //      transaction.commit();
    //    }
    //    AttributeValueService service = (AttributeValueService) DefaultSpringApplicationContext.getSpringApplicationContext().getBean(
    //        "attributeValueService");
    //    service.removeOrphanedAttributeValuesAndAssignments();
    //  }

    /**
     * Set parent of each new created {@link AbstractHierarchicalEntity} to its virtual element if its parent has not been set yet.
     * 
     * @param newInstanceChanges
     *    A {@link List} with all changes of type {@link TypeOfModelElementChange#ADD_INSTANCE}
     */
    private void setDefaultParents(List<AbstractModelElementChange> newInstanceChanges) {
        if (!newInstanceChanges.isEmpty()) {
            LOGGER.info("Setting default parents for HierarchicalEntities if necessary...");
            Map<Class<?>, AbstractHierarchicalEntity<?>> virtualElements = Maps.newHashMap();
            try {
                Method addParent = AbstractHierarchicalEntity.class.getMethod("addParent",
                        HierarchicalEntity.class);
                for (AbstractModelElementChange change : newInstanceChanges) {
                    if (change instanceof EMFInstanceExpressionCreation) {
                        EMFInstanceExpressionCreation newInstanceChange = (EMFInstanceExpressionCreation) change;
                        BuildingBlock bb = (BuildingBlock) instanceMapping.inverse()
                                .get(newInstanceChange.getNewCreatedModelExpression());
                        if (bb instanceof AbstractHierarchicalEntity<?>) {
                            setDefaultParentForHierarchicalEntity(bb, virtualElements, addParent);
                            session.save(bb);
                        }
                    } else {
                        LOGGER.error("EMFModelWriter cannoth persist change: " + change);
                    }
                }
            } catch (IllegalArgumentException e) {
                LOGGER.error(e);
                throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR, e);
            } catch (IllegalAccessException e) {
                LOGGER.error(e);
                throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR, e);
            } catch (InvocationTargetException e) {
                LOGGER.error(e);
                throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR, e);
            } catch (SecurityException e) {
                LOGGER.error(e);
                throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR, e);
            } catch (NoSuchMethodException e) {
                LOGGER.error(e);
                throw new IteraplanTechnicalException(IteraplanErrorMessages.GENERAL_TECHNICAL_ERROR, e);
            }
            LOGGER.info("Setting of default parents done.");
        }

    }

    private void setDefaultParentForHierarchicalEntity(BuildingBlock bb,
            Map<Class<?>, AbstractHierarchicalEntity<?>> virtualElements, Method addParent)
            throws IllegalAccessException, InvocationTargetException {
        LOGGER.info("Checking \"{0}\" for its parent...", bb);
        AbstractHierarchicalEntity<?> hierarchicalEntity = (AbstractHierarchicalEntity<?>) bb;
        if (hierarchicalEntity.getParent() == null) {
            LOGGER.info("\"{0}\" has no parent. Setting virtual element as parent...", bb);
            if (!virtualElements.containsKey(hierarchicalEntity.getClass())) {
                LOGGER.info("Finding virtual element for building block type \"{0}\"...",
                        bb.getClass().getSimpleName());
                String serviceName = bb.getClass().getSimpleName().substring(0, 1).toLowerCase()
                        + bb.getClass().getSimpleName().substring(1) + "Service";
                BuildingBlockService<?, ?> service = (BuildingBlockService<?, ?>) DefaultSpringApplicationContext
                        .getSpringApplicationContext().getBean(serviceName);
                Set<String> names = Sets.newHashSet(AbstractHierarchicalEntity.TOP_LEVEL_NAME);
                AbstractHierarchicalEntity<?> virtualElement = (AbstractHierarchicalEntity<?>) service
                        .findByNames(names).get(0);
                virtualElements.put(bb.getClass(), virtualElement);
            }
            AbstractHierarchicalEntity<?> virtualElement = virtualElements.get(hierarchicalEntity.getClass());
            addParent.invoke(bb, virtualElement);
            LOGGER.info("Parent of \"{0}\" set to \"{1} ({2})\"", bb, virtualElement,
                    virtualElement.getClass().getSimpleName());
        } else {
            LOGGER.info("Parent for \"{0}\" is set already.", bb);
        }
    }

    private static Map<TypeOfModelElementChange, List<AbstractModelElementChange>> initChangeMap() {
        Map<TypeOfModelElementChange, List<AbstractModelElementChange>> changeMap = Maps.newLinkedHashMap();
        for (TypeOfModelElementChange typeOfChange : TypeOfModelElementChange.ALL) {
            List<AbstractModelElementChange> emptyList = Lists.newArrayList();
            changeMap.put(typeOfChange, emptyList);
        }
        return changeMap;
    }

}