/** * 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.edb.jpa.internal; import java.util.ArrayList; import java.util.List; import org.openengsb.core.edb.api.EDBCheckException; import org.openengsb.core.edb.api.EDBCommit; import org.openengsb.core.edb.api.EDBConstants; import org.openengsb.core.edb.api.EDBException; import org.openengsb.core.edb.api.hooks.EDBPreCommitHook; import org.openengsb.core.edb.jpa.internal.dao.JPADao; import org.openengsb.core.edb.jpa.internal.util.EDBUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is the standard pre-commit hook for the EDB. It does the basic checking algorithms, fills and updates the * model version field and checks for conflicts. If any error occurs, the onPreCommit function throws an * EDBCheckException. */ public class CheckPreCommitHook implements EDBPreCommitHook { private static final Logger LOGGER = LoggerFactory.getLogger(CheckPreCommitHook.class); private JPADao dao; public CheckPreCommitHook(JPADao dao) { this.dao = dao; } @Override public void onPreCommit(EDBCommit commit) throws EDBException { if (!(commit instanceof JPACommit)) { throw new EDBException("Unsupported EDBCommit type"); } JPACommit orig = (JPACommit) commit; List<JPAObject> insertFails = null; List<JPAObject> updateFails = null; List<String> deleteFails = null; if (orig.getInsertedObjects() != null) { insertFails = checkInserts(orig.getInsertedObjects()); } if (orig.getDeletions() != null) { deleteFails = checkDeletions(orig.getDeletions()); } if (orig.getUpdatedObjects() != null) { updateFails = checkUpdates(orig.getUpdatedObjects()); } testIfExceptionNeeded(insertFails, updateFails, deleteFails); } /** * Checks all lists with failed objects if there is the need to throw an EDBCheckException. If the need is given, it * creates this exception and throws it. */ private void testIfExceptionNeeded(List<JPAObject> insertFails, List<JPAObject> updateFails, List<String> deleteFails) throws EDBCheckException { StringBuilder builder = new StringBuilder(); for (JPAObject insert : insertFails) { builder.append("Object with the oid ").append(insert.getOID()).append(" exists already. "); } for (JPAObject update : updateFails) { builder.append("Found a conflict for the oid ").append(update.getOID()).append(". "); } for (String delete : deleteFails) { builder.append("Object with the oid ").append(delete).append(" doesn't exists/is deleted. "); } if (builder.length() != 0) { EDBCheckException checkException = new EDBCheckException(builder.toString()); checkException.setFailedInserts(EDBUtils.convertJPAObjectsToEDBObjects(insertFails)); checkException.setFailedUpdates(EDBUtils.convertJPAObjectsToEDBObjects(updateFails)); checkException.setFailedDeletes(deleteFails); throw checkException; } } /** * Checks if all oid's of the given JPAObjects are not existing yet. Returns a list of objects where the JPAObject * already exists. */ private List<JPAObject> checkInserts(List<JPAObject> inserts) { List<JPAObject> failedObjects = new ArrayList<JPAObject>(); for (JPAObject insert : inserts) { String oid = insert.getOID(); if (checkIfActiveOidExisting(oid)) { failedObjects.add(insert); } else { insert.addEntry(new JPAEntry(EDBConstants.MODEL_VERSION, "1", Integer.class.getName(), insert)); } } return failedObjects; } /** * Checks if all oid's of the given JPAObjects are existing. Returns a list of objects where the JPAObject doesn't * exist. */ private List<String> checkDeletions(List<String> deletes) { List<String> failedObjects = new ArrayList<String>(); for (String delete : deletes) { if (!checkIfActiveOidExisting(delete)) { failedObjects.add(delete); } } return failedObjects; } /** * Checks every update for a potential conflict. Returns a list of objects where a conflict has been found. */ private List<JPAObject> checkUpdates(List<JPAObject> updates) throws EDBException { List<JPAObject> failedObjects = new ArrayList<JPAObject>(); for (JPAObject update : updates) { try { Integer modelVersion = investigateVersionAndCheckForConflict(update); modelVersion++; update.removeEntry(EDBConstants.MODEL_VERSION); update.addEntry(new JPAEntry(EDBConstants.MODEL_VERSION, modelVersion + "", Integer.class.getName(), update)); } catch (EDBException e) { failedObjects.add(update); } } return failedObjects; } /** * Investigates the version of an JPAObject and checks if a conflict can be found. */ private Integer investigateVersionAndCheckForConflict(JPAObject newObject) throws EDBException { JPAEntry entry = newObject.getEntry(EDBConstants.MODEL_VERSION); String oid = newObject.getOID(); Integer modelVersion = 0; if (entry != null) { modelVersion = Integer.parseInt(entry.getValue()); Integer currentVersion = dao.getVersionOfOid(oid); if (!modelVersion.equals(currentVersion)) { try { checkForConflict(newObject); } catch (EDBException e) { LOGGER.info("conflict detected, user get informed"); throw new EDBException("conflict was detected. There is a newer version of the model with the oid " + oid + " saved."); } modelVersion = currentVersion; } } else { modelVersion = dao.getVersionOfOid(oid); } return modelVersion; } /** * Simple check mechanism if there is a conflict between a model which should be saved and the existing model, based * on the values which are in the EDB. */ private void checkForConflict(JPAObject newObject) throws EDBException { String oid = newObject.getOID(); JPAObject object = dao.getJPAObject(oid); for (JPAEntry entry : newObject.getEntries()) { if (entry.getKey().equals(EDBConstants.MODEL_VERSION)) { continue; } JPAEntry rival = object.getEntry(entry.getKey()); String value = rival != null ? rival.getValue() : null; if (value == null || !value.equals(entry.getValue())) { LOGGER.debug("Conflict detected at key {} when comparing {} with {}", new Object[]{ entry.getKey(), entry.getValue(), value == null ? "null" : value }); throw new EDBException("Conflict detected. Failure when comparing the values of the key " + entry.getKey()); } } } /** * Returns true if the given oid is active right now (means is existing and not deleted) and return false otherwise. */ private boolean checkIfActiveOidExisting(String oid) { try { JPAObject obj = dao.getJPAObject(oid); if (!obj.isDeleted()) { return true; } } catch (EDBException e) { // nothing to do here } return false; } }