/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you 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 org.openengsb.core.ekb.persistence.persist.edb.internal; import static org.openengsb.core.ekb.persistence.persist.edb.internal.ModelDiff.createModelDiff; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openengsb.core.api.model.ModelDescription; import org.openengsb.core.api.model.OpenEngSBModel; import org.openengsb.core.edb.api.EDBConstants; import org.openengsb.core.edb.api.EDBException; import org.openengsb.core.edb.api.EDBObject; import org.openengsb.core.edb.api.EngineeringDatabaseService; import org.openengsb.core.ekb.api.EKBCommit; import org.openengsb.core.ekb.api.EKBException; import org.openengsb.core.ekb.api.ModelRegistry; import org.openengsb.core.ekb.api.TransformationEngine; import org.openengsb.core.ekb.api.hooks.EKBPreCommitHook; import org.openengsb.core.ekb.common.AdvancedModelWrapper; import org.openengsb.core.ekb.common.EDBConverter; import org.openengsb.core.ekb.common.EngineeringObjectModelWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The EngineeringObjectEnhancer enhance an EKBCommit object with additional models which need to be updated or enhance * inserted models based on the Engineering Object concept of the OpenEngSB. */ public class EngineeringObjectEnhancer implements EKBPreCommitHook { private static final Logger LOGGER = LoggerFactory.getLogger(EngineeringObjectEnhancer.class); // TODO: OPENENGSB-3359, replace edbService and edbConverter with queryInterface private EngineeringDatabaseService edbService; private EDBConverter edbConverter; private TransformationEngine transformationEngine; private ModelRegistry modelRegistry; private EOMode mode; public EngineeringObjectEnhancer(EngineeringDatabaseService edbService, EDBConverter edbConverter, TransformationEngine transformationEngine, ModelRegistry modelRegistry, String mode) { this.edbService = edbService; this.edbConverter = edbConverter; this.transformationEngine = transformationEngine; this.modelRegistry = modelRegistry; try { this.mode = EOMode.valueOf(mode); } catch (IllegalArgumentException e) { this.mode = EOMode.DEACTIVATED; LOGGER.error("Unknown mode setting. The engineering object enhancement will be deactivated.", e); } } @Override public void onPreCommit(EKBCommit commit) throws EKBException { if (mode == EOMode.DEACTIVATED) { return; } enhanceEKBCommit(commit); } /** * Does the Engineering Object enhancement of the EKBCommit object. In this step there may be some inserts updated * or new objects will be added to the updates of the EKBCommit object. Throws an EKBException if an error in the * Engineering Object logic occurs. */ private void enhanceEKBCommit(EKBCommit commit) throws EKBException { LOGGER.debug("Started to enhance the EKBCommit with Engineering Object information"); enhanceCommitInserts(commit); enhanceCommitUpdates(commit); // TODO: OPENENGSB-3357, consider also deletions in the enhancement LOGGER.debug("Finished EKBCommit enhancing"); } /** * Enhances the EKBCommit for the updates of EngineeringObjects. */ private void enhanceCommitUpdates(EKBCommit commit) throws EKBException { Map<Object, AdvancedModelWrapper> updated = new HashMap<Object, AdvancedModelWrapper>(); List<AdvancedModelWrapper> result = recursiveUpdateEnhancement( convertOpenEngSBModelList(commit.getUpdates()), updated, commit); commit.getUpdates().addAll(convertSimpleModelWrapperList(result)); } /** * Converts a list of OpenEngSBModel objects to a list of SimpleModelWrapper objects */ private List<AdvancedModelWrapper> convertOpenEngSBModelList(List<OpenEngSBModel> models) { List<AdvancedModelWrapper> wrappers = new ArrayList<AdvancedModelWrapper>(); for (OpenEngSBModel model : models) { wrappers.add(AdvancedModelWrapper.wrap(model)); } return wrappers; } /** * Converts a list of SimpleModelWrapper objects to a list of OpenEngSBModel objects */ private List<OpenEngSBModel> convertSimpleModelWrapperList(List<AdvancedModelWrapper> wrappers) { List<OpenEngSBModel> models = new ArrayList<OpenEngSBModel>(); for (AdvancedModelWrapper wrapper : wrappers) { models.add(wrapper.getUnderlyingModel()); } return models; } /** * Recursive function for calculating all models which need to be updated due to the original updates of the * EKBCommit. */ private List<AdvancedModelWrapper> recursiveUpdateEnhancement(List<AdvancedModelWrapper> updates, Map<Object, AdvancedModelWrapper> updated, EKBCommit commit) { List<AdvancedModelWrapper> additionalUpdates = enhanceUpdates(updates, updated, commit); for (AdvancedModelWrapper model : updates) { updated.put(model.getCompleteModelOID(), model); } if (!additionalUpdates.isEmpty()) { additionalUpdates.addAll(recursiveUpdateEnhancement(additionalUpdates, updated, commit)); } return additionalUpdates; } /** * Enhances the given list of updates and returns a list of models which need to be additionally updated. */ private List<AdvancedModelWrapper> enhanceUpdates(List<AdvancedModelWrapper> updates, Map<Object, AdvancedModelWrapper> updated, EKBCommit commit) { List<AdvancedModelWrapper> additionalUpdates = new ArrayList<AdvancedModelWrapper>(); for (AdvancedModelWrapper model : updates) { if (updated.containsKey(model.getCompleteModelOID())) { continue; // this model was already updated in this commit } if (model.isEngineeringObject()) { additionalUpdates.addAll(performEOModelUpdate(model.toEngineeringObject(), commit)); } additionalUpdates.addAll(getReferenceBasedUpdates(model, updated, commit)); } return additionalUpdates; } /** * Runs the logic of updating an Engineering Object model. Returns a list of models which need to be updated * additionally. */ private List<AdvancedModelWrapper> performEOModelUpdate(EngineeringObjectModelWrapper model, EKBCommit commit) { ModelDiff diff = createModelDiff(model.getUnderlyingModel(), model.getCompleteModelOID(), edbService, edbConverter); boolean referencesChanged = diff.isForeignKeyChanged(); boolean valuesChanged = diff.isValueChanged(); // TODO: OPENENGSB-3358, Make it possible to change references and values at the same time. Should be // no too big deal since we already know at this point which values of the model have been changed and // what the old and the new value for the changed properties are if (referencesChanged && valuesChanged) { throw new EKBException("Engineering Objects may be updated only at " + "references or at values not both in the same commit"); } if (referencesChanged) { reloadReferencesAndUpdateEO(diff, model); } else { return updateReferencedModelsByEO(model); } return new ArrayList<AdvancedModelWrapper>(); } /** * Updates all models which are referenced by the given engineering object. */ private List<AdvancedModelWrapper> updateReferencedModelsByEO(EngineeringObjectModelWrapper model) { List<AdvancedModelWrapper> updates = new ArrayList<AdvancedModelWrapper>(); for (Field field : model.getForeignKeyFields()) { try { AdvancedModelWrapper result = performMerge(model, loadReferencedModel(model, field)); if (result != null) { updates.add(result); } } catch (EDBException e) { LOGGER.debug("Skipped referenced model for field {}, since it does not exist.", field, e); } } return updates; } /** * Reload the references which have changed in the actual update and update the Engineering Object accordingly. */ private void reloadReferencesAndUpdateEO(ModelDiff diff, EngineeringObjectModelWrapper model) { for (ModelDiffEntry entry : diff.getDifferences().values()) { mergeEngineeringObjectWithReferencedModel(entry.getField(), model); } } /** * Returns engineering objects to the commit, which are changed by a model which was committed in the EKBCommit */ private List<AdvancedModelWrapper> getReferenceBasedUpdates(AdvancedModelWrapper model, Map<Object, AdvancedModelWrapper> updated, EKBCommit commit) throws EKBException { List<AdvancedModelWrapper> updates = new ArrayList<AdvancedModelWrapper>(); List<EDBObject> references = model.getModelsReferringToThisModel(edbService); for (EDBObject reference : references) { EDBModelObject modelReference = new EDBModelObject(reference, modelRegistry, edbConverter); AdvancedModelWrapper ref = updateEOByUpdatedModel(modelReference, model, updated); if (!updated.containsKey(ref.getCompleteModelOID())) { updates.add(ref); } } return updates; } /** * Updates an Engineering Object given as EDBObject based on the update on the given model which is referenced by * the given Engineering Object. */ private AdvancedModelWrapper updateEOByUpdatedModel(EDBModelObject reference, AdvancedModelWrapper model, Map<Object, AdvancedModelWrapper> updated) { ModelDescription source = model.getModelDescription(); ModelDescription description = reference.getModelDescription(); AdvancedModelWrapper wrapper = updated.get(reference.getOID()); Object ref = null; if (wrapper == null) { ref = reference.getCorrespondingModel(); } else { ref = wrapper.getUnderlyingModel(); } Object transformResult = transformationEngine.performTransformation(source, description, model.getUnderlyingModel(), ref); return AdvancedModelWrapper.wrap(transformResult); } /** * Enhances the EKBCommit for the insertion of EngineeringObjects. */ private void enhanceCommitInserts(EKBCommit commit) throws EKBException { for (OpenEngSBModel model : commit.getInserts()) { AdvancedModelWrapper simple = AdvancedModelWrapper.wrap(model); if (simple.isEngineeringObject()) { performInsertEOLogic(simple.toEngineeringObject()); } } } /** * Performs the logic for the enhancement needed to be performed to insert an Engineering Object into the EDB. */ private void performInsertEOLogic(EngineeringObjectModelWrapper model) { for (Field field : model.getForeignKeyFields()) { mergeEngineeringObjectWithReferencedModel(field, model); } } /** * Performs the merge from the source model to the target model and returns the result. Returns null if either the * source or the target is null. */ private AdvancedModelWrapper performMerge(AdvancedModelWrapper source, AdvancedModelWrapper target) { if (source == null || target == null) { return null; } ModelDescription sourceDesc = source.getModelDescription(); ModelDescription targetDesc = target.getModelDescription(); Object transformResult = transformationEngine.performTransformation(sourceDesc, targetDesc, source.getUnderlyingModel(), target.getUnderlyingModel()); AdvancedModelWrapper wrapper = AdvancedModelWrapper.wrap(transformResult); wrapper.removeOpenEngSBModelEntry(EDBConstants.MODEL_VERSION); return wrapper; } /** * Merges the given EngineeringObject with the referenced model which is defined in the given field. */ private void mergeEngineeringObjectWithReferencedModel(Field field, EngineeringObjectModelWrapper model) { AdvancedModelWrapper result = performMerge(loadReferencedModel(model, field), model); if (result != null) { model = result.toEngineeringObject(); } } private AdvancedModelWrapper loadReferencedModel(EngineeringObjectModelWrapper eo, Field field) { return eo.loadReferencedModel(field, modelRegistry, edbService, edbConverter); } }