de.iteratec.iteraplan.businesslogic.exchange.elasticmi.read.MiJsonMicroImportProcess.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.exchange.elasticmi.read.MiJsonMicroImportProcess.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.businesslogic.exchange.elasticmi.read;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;

import de.iteratec.iteraplan.businesslogic.exchange.common.ImportProcess.CheckPoint;
import de.iteratec.iteraplan.businesslogic.exchange.common.ImportProcessMessages;
import de.iteratec.iteraplan.businesslogic.exchange.common.SimpleMessage;
import de.iteratec.iteraplan.businesslogic.exchange.elasticmi.IteraplanMiLoadTaskFactory;
import de.iteratec.iteraplan.businesslogic.exchange.elasticmi.write.model.MiIteraplanDiffWriter;
import de.iteratec.iteraplan.businesslogic.service.AttributeValueService;
import de.iteratec.iteraplan.businesslogic.service.BuildingBlockServiceLocator;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.elasticmi.ElasticMiContext;
import de.iteratec.iteraplan.elasticmi.diff.model.CreateDiff;
import de.iteratec.iteraplan.elasticmi.diff.model.DeleteDiff;
import de.iteratec.iteraplan.elasticmi.diff.model.MergeStrategy;
import de.iteratec.iteraplan.elasticmi.diff.model.ModelDiff;
import de.iteratec.iteraplan.elasticmi.diff.model.UpdateDiff;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.CUDMergeStrategy;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.DeleteDiffImpl;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.ElasticMiModelMergeable;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.ModelDiffImpl;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.ModelMatcherImpl;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.ModelMergeable;
import de.iteratec.iteraplan.elasticmi.diff.model.impl.PartialModelDiffTransformer;
import de.iteratec.iteraplan.elasticmi.dynamic.DynamicMetamodel;
import de.iteratec.iteraplan.elasticmi.dynamic.DynamicMetamodelImpl;
import de.iteratec.iteraplan.elasticmi.dynamic.FilterPredicate;
import de.iteratec.iteraplan.elasticmi.io.mapper.json.JsonSingleOEMapper;
import de.iteratec.iteraplan.elasticmi.messages.Message.Severity;
import de.iteratec.iteraplan.elasticmi.messages.MessageListener;
import de.iteratec.iteraplan.elasticmi.messages.MiMessageAccess;
import de.iteratec.iteraplan.elasticmi.messages.merge.CreateDiffMessage;
import de.iteratec.iteraplan.elasticmi.messages.merge.DeleteDiffMessage;
import de.iteratec.iteraplan.elasticmi.messages.merge.UpdateDiffMessage;
import de.iteratec.iteraplan.elasticmi.metamodel.common.impl.atomic.AtomicDataType;
import de.iteratec.iteraplan.elasticmi.metamodel.common.impl.atomic.IntegerAtomicDataType;
import de.iteratec.iteraplan.elasticmi.metamodel.partial.BasePartialExportMetamodel;
import de.iteratec.iteraplan.elasticmi.metamodel.partial.b.BPartialExportMetamodel;
import de.iteratec.iteraplan.elasticmi.metamodel.read.RMetamodel;
import de.iteratec.iteraplan.elasticmi.metamodel.read.RPropertyExpression;
import de.iteratec.iteraplan.elasticmi.metamodel.read.RStructuredTypeExpression;
import de.iteratec.iteraplan.elasticmi.metamodel.read.mask.MaskingMetamodel;
import de.iteratec.iteraplan.elasticmi.metamodel.read.mask.MetamodelWithPermissionsMaskUtil;
import de.iteratec.iteraplan.elasticmi.metamodel.read.pojo.PojoRMetamodelWithPermissionsCopier;
import de.iteratec.iteraplan.elasticmi.model.Model;
import de.iteratec.iteraplan.elasticmi.model.ModelUtil;
import de.iteratec.iteraplan.elasticmi.model.ObjectExpression;
import de.iteratec.iteraplan.elasticmi.permission.ElasticMiAccessLevel;
import de.iteratec.iteraplan.elasticmi.permission.ElasticMiTypePermission;
import de.iteratec.iteraplan.elasticmi.validate.model.ModelValidator;

/**
 * ImportProcess for Json's with an single ObjectExpression.
 * This ImportProcess do not extends the MiImportProcess or ImportProcess because this ImportProcess special variation and has his own methods.
 */
public class MiJsonMicroImportProcess implements JsonMicroImportProcess {

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

    private static final String VALIDATE_ERROR_MESSAGE_KEY = "json.import.exception.notValid";
    private static final String DELETE_EXCEPTION_MESSAGE_KEY = "json.delete.exception";
    private static final String DELETE_NOT_FOUND_MESSAGE_KEY = "json.delete.exception.elementNotFound";
    private static final String DELETE_NO_PERMISSIONS_MESSAGE_KEY = "json.delete.exception.noPermissions";
    private static final String CREATE_EXCEPTION_MESSAGE_KEY = "json.create.exception";
    private static final String CREATE_NO_PERMISSIONS_MESSAGE_KEY = "json.create.exception.noPermissions";
    private static final String UPDATE_EXCEPTION_MESSAGE_KEY = "json.update.exception";
    private static final String UPDATE_NOT_FOUND_MESSAGE_KEY = "json.update.exception.elementNotFound";
    private static final String UPDATE_NO_PERMISSIONS_MESSAGE_KEY = "json.update.exception.noPermissions";

    private static final String FILTERED_PROP_NAME = "id";

    private final BuildingBlockServiceLocator bbServiceLocator;
    private final AttributeValueService attributeValueService;

    private final MiImportProcessMessages importProcessMessages = new MiImportProcessMessages();
    private final ImportDatabaseDataAccess databaseAccess;

    private MaskingMetamodel maskingMetamodel;

    private BigInteger idValue;
    private RStructuredTypeExpression mainType;
    private JsonObject json = null;

    private Model modelToImport;
    private final Model preImportModel;

    private boolean success = true;

    /**
     * Default constructor.
     * @param bbServiceLocator
     * @param attributeValueService
     * @param loadTaskFactory
     */
    public MiJsonMicroImportProcess(BuildingBlockServiceLocator bbServiceLocator,
            AttributeValueService attributeValueService, IteraplanMiLoadTaskFactory loadTaskFactory) {

        this.bbServiceLocator = bbServiceLocator;
        this.attributeValueService = attributeValueService;
        this.databaseAccess = new ImportDatabaseDataAccess(loadTaskFactory);
        this.preImportModel = ModelUtil.copy(databaseAccess.getRMetamodel(), databaseAccess.getModel());
    }

    /**
     * Set id, baseType and parse the inputStream to an JsonObject
     * @param id
     * @param baseType
     * @param in
     */
    private void init(BigInteger id, RStructuredTypeExpression baseType, InputStream in) {
        this.idValue = id;
        this.mainType = baseType;
        this.json = readJsonFromInputStream(in);
    }

    /**
     * Parse the inputStream to an JsonObject.
     * If the inputStream is null, null is returned and the {@link #importProcessMessages} get an error message.
     * 
     * @param inputStream
     * @return JsonObject
     */
    private JsonObject readJsonFromInputStream(InputStream inputStream) {
        if (inputStream == null) {
            LOGGER.error("The input stream to read the json from was null.");
            return null;
        }

        try {
            JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
            return new JsonParser().parse(reader).getAsJsonObject();
        } catch (UnsupportedEncodingException e) {
            LOGGER.error("Error reading json", e);
        } catch (IllegalStateException e) {
            LOGGER.error("Error reading json", e);
        }
        return null;
    }

    @Override
    public BigInteger create(RStructuredTypeExpression baseType, InputStream in) {
        init(null, baseType, in);

        if (!isJsonValid()) {
            return null;
        }

        // check user permission
        if (!hasPermission(ElasticMiAccessLevel.CREATE)) {
            getImportProcessMessages().onMessage(new LocalizedIteraplanMessage(Severity.WARNING,
                    CREATE_NO_PERMISSIONS_MESSAGE_KEY, mainType, idValue));
            return null;
        }

        // create an id for the new oe
        this.idValue = getCanonicModel().getIdProvider().getId();

        BigInteger createdID = executeImport();
        if (hasFailed()) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, CREATE_EXCEPTION_MESSAGE_KEY));
            return null;
        }

        return createdID;
    }

    private boolean hasFailed() {
        return !success || !importProcessMessages.getMessages(Severity.ERROR).isEmpty();
    }

    @Override
    public void update(BigInteger id, RStructuredTypeExpression baseType, InputStream in) {
        init(id, baseType, in);

        if (!isJsonValid()) {
            return;
        }

        if (!hasPermission(ElasticMiAccessLevel.UPDATE)) {
            getImportProcessMessages().onMessage(
                    new LocalizedIteraplanMessage(Severity.WARNING, UPDATE_NO_PERMISSIONS_MESSAGE_KEY, idValue));
            return;
        }

        ObjectExpression oeToUpdate = getCanonicModel().findById(mainType, idValue);
        if (oeToUpdate == null) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, UPDATE_NOT_FOUND_MESSAGE_KEY));
            return;
        }

        executeImport();
        if (hasFailed()) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, UPDATE_EXCEPTION_MESSAGE_KEY));
        }
    }

    private boolean isJsonValid() {
        if (json == null || importProcessMessages.getMessages(Severity.ERROR).size() > 0) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, VALIDATE_ERROR_MESSAGE_KEY));
            return false;
        }
        return true;
    }

    private boolean hasPermission(ElasticMiAccessLevel accessLevel) {
        ElasticMiTypePermission permission = new ElasticMiTypePermission(mainType.getPersistentName(), accessLevel);
        return ElasticMiContext.getCurrentContext().getAccessController().has(permission);
    }

    @Override
    public void delete(BigInteger id, RStructuredTypeExpression baseType) {
        this.idValue = id;
        this.mainType = baseType;

        if (!hasPermission(ElasticMiAccessLevel.DELETE)) {
            getImportProcessMessages().onMessage(new LocalizedIteraplanMessage(Severity.WARNING,
                    DELETE_NO_PERMISSIONS_MESSAGE_KEY, mainType, idValue));
            return;
        }

        ObjectExpression oeToDelete = getCanonicModel().findById(mainType, idValue);
        if (oeToDelete == null) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, DELETE_NOT_FOUND_MESSAGE_KEY));
            // The element that should be deleted, could not be found. Cancel the operation.
            return;
        }

        ModelDiff modelDiff = new SimpleModelDeleteDiff(mainType, oeToDelete);

        // merger into the Database
        mergeModelIntoDb(modelDiff);
        if (hasFailed()) {
            getImportProcessMessages().onMessage(
                    new LocalizedIteraplanMessage(Severity.ERROR, DELETE_EXCEPTION_MESSAGE_KEY, mainType, idValue));
        }
    }

    /**
     * Execute the import process.
     * @return The ID of the merged object expression, null if merge wasn't successful
     */
    private BigInteger executeImport() {

        creatImportModelAndValidate();
        dryRun();
        if (success) {
            return mergeModelIntoDb();
        } else {
            return null;
        }
    }

    /**
     * Create the model to Import and validate it.
     */
    public void creatImportModelAndValidate() {

        // check if it is an valid json
        if (!validate()) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, VALIDATE_ERROR_MESSAGE_KEY));
            success = false;
            return;
        }

        // read the json and create a model
        modelToImport = loadModelFromJson();

        // validate the generated model
        ModelValidator preMergeValidator = IteraplanModelValidators.getPreMergeModelValidator(getImportMetamodel());
        boolean modelIsValid = preMergeValidator.validate(modelToImport, getImportProcessMessages());

        modelIsValid &= !getImportProcessMessages().hasErrors();
        if (!modelIsValid) {
            importProcessMessages
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, VALIDATE_ERROR_MESSAGE_KEY));
            success = false;
        }
    }

    /**
     * Create the model from the json
     * @return model to import
     */
    private Model loadModelFromJson() {
        JsonSingleOEMapper soeMapper = new JsonSingleOEMapper((BasePartialExportMetamodel) getImportMetamodel(),
                maskingMetamodel, idValue);
        return modelToImport = soeMapper.read(json, getCanonicModel().getIdProvider());
    }

    /**
     * Check if the json is valid.
     * The JsonObject must contain an result and the result must be a JsonArray. 
     * @return true if the Json is valid, otherwise false
     */
    private boolean validate() {
        JsonElement result = json.get("result");
        if (result == null || !result.isJsonArray()) {
            getImportProcessMessages()
                    .onMessage(new LocalizedIteraplanMessage(Severity.ERROR, "json.import.exception.notValid"));
            return false;
        }
        return true;
    }

    private void dryRun() {

        Model modelCopy = ModelUtil.copy(getCanonicMetamodel(), getCanonicModel());

        updateMaskingMetamodelBase(); // necessary to respect imported attributes
        ModelDiff modelDiff = new ModelDiffImpl(getImportMetamodel(), modelCopy, getModelToImport(),
                new ModelMatcherImpl(), getImportProcessMessages());

        modelDiff = prepareDiff(modelDiff);
        modelDiff.merge(getMergeStrategy(new ElasticMiModelMergeable(modelCopy)));

        ModelValidator postMergeValidator = IteraplanModelValidators
                .getPostMergeModelValidator(getCanonicMetamodel());
        success = postMergeValidator.validate(modelCopy, getImportProcessMessages()) && success; // success on the right, since validation should be done in any case

        if (success) {
            importProcessMessages.clear();
        }
    }

    /**
     * Create first the ModelDiff and merge this into the database.
     * @return ID of the merged building block
     */
    private BigInteger mergeModelIntoDb() {
        databaseAccess.loadMetamodel();
        ModelDiff modelDiff = new ModelDiffImpl(getImportMetamodel(), getCanonicModel(), getModelToImport(),
                new ModelMatcherImpl(), getImportProcessMessages());
        modelDiff = prepareDiff(modelDiff);

        mergeModelIntoDb(modelDiff);
        if (modelDiff.getCreateDiffs().size() == 1) {
            return modelDiff.getCreateDiffs().iterator().next().getIdAfterMerge();
        } else {
            return idValue;
        }
    }

    /**
     * Merges the passed ModelDiff into the Database.
     * @param modelDiff
     */
    private void mergeModelIntoDb(ModelDiff modelDiff) {

        MiIteraplanDiffWriter diffWriter = new MiIteraplanDiffWriter(modelDiff, attributeValueService,
                bbServiceLocator, databaseAccess.getMetamodelMapping(), databaseAccess.getInstanceMapping(),
                getImportProcessMessages());

        success = diffWriter.writeDifferences(getMergeStrategy(diffWriter)) && success; // success on the right, since validation should be done in any case

        if (success) {
            databaseAccess.loadModelWithContext();
            getImportProcessMessages().onMessage(new SimpleMessage(Severity.INFO,
                    MiMessageAccess.getString("de.iteratec.iteraplan.elasticmi.excelimport.mergeHeadline")));
            addImportResult(CheckPoint.MODEL_WRITE, getCanonicModel());
        }
    }

    private Model getModelToImport() {
        return modelToImport;
    }

    /**
     * Rebase masking metamodel with current permissions-restricted metamodel
     */
    private void updateMaskingMetamodelBase() {
        RMetamodel rMetamodel = PojoRMetamodelWithPermissionsCopier.copyRMetamodel(databaseAccess.getRMetamodel(),
                ElasticMiContext.getCurrentContext().getAccessController());
        if (maskingMetamodel == null) {
            maskingMetamodel = MetamodelWithPermissionsMaskUtil.mask(rMetamodel);
        } else {
            maskingMetamodel.reBase(rMetamodel);
        }
    }

    /**
     * Transforms the given ModelDiff from a partial to a total reference frame
     */
    private ModelDiff prepareDiff(ModelDiff modelDiff) {
        //note: no need to update masking metamodel here,
        //since partial metamodel does not support metamodel changes
        BasePartialExportMetamodel partialMetamodel = (BPartialExportMetamodel) getImportMetamodel();
        return (new PartialModelDiffTransformer()).transformToCanonic(partialMetamodel, modelDiff,
                getImportProcessMessages());
    }

    /**
     * Create an return the partialExportMetamodel
     * @return BPartialExportMetamodel
     */
    private RMetamodel getImportMetamodel() {

        // create the MaskingMetamodel
        RMetamodel rMetamodel = PojoRMetamodelWithPermissionsCopier.copyRMetamodel(getCanonicMetamodel(),
                ElasticMiContext.getCurrentContext().getAccessController());
        if (maskingMetamodel == null) {
            maskingMetamodel = MetamodelWithPermissionsMaskUtil.mask(rMetamodel);
        } else {
            maskingMetamodel.reBase(rMetamodel);
        }

        // create the filter predicate
        DynamicMetamodel dMetamodel = new DynamicMetamodelImpl();
        RPropertyExpression filterProp = mainType.findPropertyByPersistentName(FILTERED_PROP_NAME);
        FilterPredicate pred = dMetamodel.createSimplePredicate(filterProp,
                AtomicDataType.INTEGER.type().findComparisonOperator(IntegerAtomicDataType.EQUALS_PERSISTENT_NAME),
                idValue);

        RStructuredTypeExpression filteredMainType = dMetamodel.createFilteredStructuredType(mainType, pred);

        return new BPartialExportMetamodel(maskingMetamodel, filteredMainType);
    }

    private final MergeStrategy getMergeStrategy(ModelMergeable mergeable) {
        return new CUDMergeStrategy(mergeable, new Date(), getCanonicModel(), getImportProcessMessages());
    }

    /**{@inheritDoc}**/
    @Override
    public ImportProcessMessages getImportProcessMessages() {
        return importProcessMessages;
    }

    private Model getCanonicModel() {
        return databaseAccess.getModel();
    }

    private RMetamodel getCanonicMetamodel() {
        return databaseAccess.getRMetamodel();
    }

    private void addImportResult(CheckPoint modelCompare, Model mergedModel) {
        ModelDiff diff = new ModelDiffImpl(getCanonicMetamodel(), preImportModel, mergedModel,
                new ModelMatcherImpl(), MessageListener.NOOP_LISTENER);
        for (CreateDiff create : diff.getCreateDiffs()) {
            getImportProcessMessages().onMessage(new CreateDiffMessage(create));
        }
        for (UpdateDiff update : diff.getUpdateDiffs()) {
            getImportProcessMessages().onMessage(new UpdateDiffMessage(update));
        }
        for (DeleteDiff delete : diff.getDeleteDiffs()) {
            getImportProcessMessages().onMessage(new DeleteDiffMessage(delete));
        }
        importProcessMessages.finalizeCheckPoint(modelCompare);
    }

    private static final class SimpleModelDeleteDiff implements ModelDiff {

        private final DeleteDiff deleteDiff;

        public SimpleModelDeleteDiff(RStructuredTypeExpression type, ObjectExpression oe) {
            deleteDiff = new DeleteDiffImpl(type, oe);
        }

        @Override
        public Set<CreateDiff> getCreateDiffs() {
            return Collections.emptySet();
        }

        @Override
        public Set<CreateDiff> getCreateDiffsForType(RStructuredTypeExpression structuredType) {
            return Collections.emptySet();
        }

        @Override
        public Set<UpdateDiff> getUpdateDiffs() {
            return Collections.emptySet();
        }

        @Override
        public Set<UpdateDiff> getUpdateDiffsForType(RStructuredTypeExpression structuredType) {
            return Collections.emptySet();
        }

        @Override
        public Set<DeleteDiff> getDeleteDiffs() {
            return Sets.newHashSet(deleteDiff);
        }

        @Override
        public Set<DeleteDiff> getDeleteDiffsForType(RStructuredTypeExpression structuredType) {
            if (deleteDiff.getStructuredType().getCanonicBase().equals(structuredType)) {
                return Sets.newHashSet(deleteDiff);
            } else {
                return Collections.emptySet();
            }
        }

        /**{@inheritDoc}**/
        @Override
        public void merge(MergeStrategy strategy) {
            // nop
        }
    }
}