/** * 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 javax.persistence.EntityManager; import org.openengsb.core.edb.api.EDBCommit; 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.edb.api.hooks.EDBBeginCommitHook; import org.openengsb.core.edb.api.hooks.EDBErrorHook; import org.openengsb.core.edb.api.hooks.EDBPostCommitHook; import org.openengsb.core.edb.api.hooks.EDBPreCommitHook; import org.openengsb.core.edb.jpa.internal.util.EDBUtils; import org.osgi.service.blueprint.container.ServiceUnavailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The AbstractEDBService is used to encapsulate the commit logic of the EDB. */ public abstract class AbstractEDBService implements EngineeringDatabaseService { protected EntityManager entityManager; private final Logger logger; private final Boolean revisionCheckEnabled; private final List<EDBErrorHook> errorHooks; private final List<EDBPostCommitHook> postCommitHooks; private final List<EDBPreCommitHook> preCommitHooks; private final List<EDBBeginCommitHook> beginCommitHooks; public AbstractEDBService(List<EDBBeginCommitHook> beginCommitHooks, List<EDBPreCommitHook> preCommitHooks, List<EDBPostCommitHook> postCommitHooks, List<EDBErrorHook> errorHooks, Boolean revisionCheckEnabled, Class<?> implementingClass) { this.beginCommitHooks = beginCommitHooks != null ? beginCommitHooks : new ArrayList<EDBBeginCommitHook>(); this.preCommitHooks = preCommitHooks != null ? preCommitHooks : new ArrayList<EDBPreCommitHook>(); this.postCommitHooks = postCommitHooks != null ? postCommitHooks : new ArrayList<EDBPostCommitHook>(); this.errorHooks = errorHooks != null ? errorHooks : new ArrayList<EDBErrorHook>(); this.revisionCheckEnabled = revisionCheckEnabled; logger = LoggerFactory.getLogger(implementingClass); } /** * Performs the actual commit logic for the EDB, including the hooks and the revision checking. */ protected Long performCommitLogic(EDBCommit commit) throws EDBException { if (!(commit instanceof JPACommit)) { throw new EDBException("The given commit type is not supported."); } if (commit.isCommitted()) { throw new EDBException("EDBCommit is already commitet."); } if (revisionCheckEnabled && commit.getParentRevisionNumber() != null && !commit.getParentRevisionNumber().equals(getCurrentRevisionNumber())) { throw new EDBException("EDBCommit do not have the correct head revision number."); } runBeginCommitHooks(commit); EDBException exception = runPreCommitHooks(commit); if (exception != null) { return runErrorHooks(commit, exception); } Long timestamp = performCommit((JPACommit) commit); runEDBPostHooks(commit); return timestamp; } /** * Does the actual commit work (JPA related actions) and returns the timestamp when the commit was done. Throws an * EDBException if an error occurs. */ private Long performCommit(JPACommit commit) throws EDBException { synchronized (entityManager) { long timestamp = System.currentTimeMillis(); try { beginTransaction(); persistCommitChanges(commit, timestamp); commitTransaction(); } catch (Exception ex) { try { rollbackTransaction(); } catch (Exception e) { throw new EDBException("Failed to rollback transaction to EDB", e); } throw new EDBException("Failed to commit transaction to EDB", ex); } return timestamp; } } /** * Add all the changes which are done through the given commit object to the entity manager. */ private void persistCommitChanges(JPACommit commit, Long timestamp) { commit.setTimestamp(timestamp); addModifiedObjectsToEntityManager(commit.getJPAObjects(), timestamp); commit.setCommitted(true); logger.debug("persisting JPACommit"); entityManager.persist(commit); logger.debug("mark the deleted elements as deleted"); updateDeletedObjectsThroughEntityManager(commit.getDeletions(), timestamp); } /** * Updates all modified EDBObjects with the timestamp and persist them through the entity manager. */ private void addModifiedObjectsToEntityManager(List<JPAObject> modified, Long timestamp) { for (JPAObject update : modified) { update.setTimestamp(timestamp); entityManager.persist(update); } } /** * Updates all deleted objects with the timestamp, mark them as deleted and persist them through the entity manager. */ private void updateDeletedObjectsThroughEntityManager(List<String> oids, Long timestamp) { for (String id : oids) { EDBObject o = new EDBObject(id); o.updateTimestamp(timestamp); o.setDeleted(true); JPAObject j = EDBUtils.convertEDBObjectToJPAObject(o); entityManager.persist(j); } } /** * Runs all registered begin commit hooks on the EDBCommit object. Logs exceptions which occurs in the hooks, except * for ServiceUnavailableExceptions and EDBExceptions. If an EDBException occurs, it is thrown and so returned to * the calling instance. */ private void runBeginCommitHooks(EDBCommit commit) throws EDBException { for (EDBBeginCommitHook hook : beginCommitHooks) { try { hook.onStartCommit(commit); } catch (ServiceUnavailableException e) { // Ignore } catch (EDBException e) { throw e; } catch (Exception e) { logger.error("Error while performing EDBBeginCommitHook", e); } } } /** * Runs all registered pre commit hooks on the EDBCommit object. Logs exceptions which occurs in the hooks, except * for ServiceUnavailableExceptions and EDBExceptions. If an EDBException occurs, the function returns this * exception. */ private EDBException runPreCommitHooks(EDBCommit commit) { EDBException exception = null; for (EDBPreCommitHook hook : preCommitHooks) { try { hook.onPreCommit(commit); } catch (ServiceUnavailableException e) { // Ignore } catch (EDBException e) { exception = e; break; } catch (Exception e) { logger.error("Error while performing EDBPreCommitHook", e); } } return exception; } /** * Runs all registered error hooks on the EDBCommit object. Logs exceptions which occurs in the hooks, except for * ServiceUnavailableExceptions and EDBExceptions. If an EDBException occurs, the function overrides the cause of * the error with the new Exception. If an error hook returns a new EDBCommit, the EDB tries to persist this commit * instead. */ private Long runErrorHooks(EDBCommit commit, EDBException exception) throws EDBException { for (EDBErrorHook hook : errorHooks) { try { EDBCommit newCommit = hook.onError(commit, exception); if (newCommit != null) { return commit(newCommit); } } catch (ServiceUnavailableException e) { // Ignore } catch (EDBException e) { exception = e; break; } catch (Exception e) { logger.error("Error while performing EDBErrorHook", e); } } throw exception; } /** * Runs all registered post commit hooks on the EDBCommit object. Logs exceptions which occurs in the hooks, except * for ServiceUnavailableExceptions. */ private void runEDBPostHooks(EDBCommit commit) { for (EDBPostCommitHook hook : postCommitHooks) { try { hook.onPostCommit(commit); } catch (ServiceUnavailableException e) { // Ignore } catch (Exception e) { logger.error("Error while performing EDBPostCommitHook", e); } } } protected void performDeleteLogic(JPACommit commit, List<JPAObject> deletedObjects) { synchronized (entityManager) { try { beginTransaction(); for (JPAObject deletedObject : deletedObjects) { entityManager.remove(deletedObject); } entityManager.remove(commit); commitTransaction(); logger.info("Deleted commit " + commit.getRevisionNumber()); } catch (Exception ex) { try { rollbackTransaction(); } catch (Exception e) { throw new EDBException("Failed to rollback transaction to EDB", e); } throw new EDBException("Failed to commit transaction to EDB", ex); } } } protected void beginTransaction() { } protected void commitTransaction() { } protected void rollbackTransaction() { } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } public Logger getLogger() { return logger; } }