From 66e70fce7583d525521fb4f848bc194ebb0c11a8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 26 Feb 2014 20:43:26 +0100 Subject: [PATCH] [New] Implemented StrolchTransaction.addCommand(), Command.undo() Commands are now supposed to be added to the StrolchTransaction so that when a TX is committed, the commands are first verified and then Command.doCommand() is called. But most important of all, in case of an exception, Command.undo() is called so that each Command can properly undo its changes so that also in memory changes are rolled back. --- .../persistence/api/AbstractTransaction.java | 93 +++++++++++++++---- .../persistence/api/StrolchTransaction.java | 3 + .../java/li/strolch/service/api/Command.java | 2 + 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index b2060d8a9..8630186e7 100644 --- a/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -16,6 +16,7 @@ package li.strolch.persistence.api; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -38,6 +39,7 @@ import li.strolch.model.query.OrderQuery; import li.strolch.model.query.ResourceQuery; import li.strolch.persistence.inmemory.InMemoryTransaction; import li.strolch.runtime.observer.ObserverHandler; +import li.strolch.service.api.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,10 +59,12 @@ public abstract class AbstractTransaction implements StrolchTransaction { private boolean suppressUpdates; private TransactionResult txResult; + private List commands; private Set lockedElements; public AbstractTransaction(StrolchRealm realm) { this.realm = realm; + this.commands = new ArrayList<>(); this.lockedElements = new HashSet<>(); this.closeStrategy = TransactionCloseStrategy.COMMIT; this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), new Date()); @@ -146,6 +150,11 @@ public abstract class AbstractTransaction implements StrolchTransaction { } } + @Override + public void addCommand(Command command) { + this.commands.add(command); + } + @Override public ResourceMap getResourceMap() { return this.realm.getResourceMap(); @@ -229,11 +238,26 @@ public abstract class AbstractTransaction implements StrolchTransaction { try { this.txResult.setState(TransactionState.COMMITTING); + + validateCommands(); + doCommands(); commit(this.txResult); - handleCommit(start); + + long observerUpdateStart = System.nanoTime(); + updateObservers(); + long observerUpdateDuration = System.nanoTime() - observerUpdateStart; + + handleCommit(start, observerUpdateDuration); + } catch (Exception e) { this.txResult.setState(TransactionState.ROLLING_BACK); - handleFailure(start, e); + undoCommands(); + try { + rollback(txResult); + handleRollback(start); + } catch (Exception e1) { + handleFailure(start, e); + } } finally { unlockElements(); } @@ -242,10 +266,9 @@ public abstract class AbstractTransaction implements StrolchTransaction { @Override public void autoCloseableRollback() { long start = System.nanoTime(); - if (logger.isDebugEnabled()) { - logger.info("Rolling back TX for realm " + getRealmName() + "..."); //$NON-NLS-1$ - } + logger.warn("Rolling back TX for realm " + getRealmName() + "..."); //$NON-NLS-1$ try { + undoCommands(); rollback(this.txResult); handleRollback(start); } catch (Exception e) { @@ -259,7 +282,7 @@ public abstract class AbstractTransaction implements StrolchTransaction { protected abstract void rollback(TransactionResult txResult) throws Exception; - private void handleCommit(long start) { + private void handleCommit(long start, long observerUpdateDuration) { long end = System.nanoTime(); long txDuration = end - txResult.getStartNanos(); @@ -276,19 +299,9 @@ public abstract class AbstractTransaction implements StrolchTransaction { sb.append(StringHelper.formatNanoDuration(txDuration)); sb.append(" with close operation taking "); //$NON-NLS-1$ sb.append(StringHelper.formatNanoDuration(closeDuration)); + sb.append(" and observer updates took "); + sb.append(StringHelper.formatNanoDuration(observerUpdateDuration)); logger.info(sb.toString()); - - if (!this.suppressUpdates && this.observerHandler != null) { - - Set keys = this.txResult.getKeys(); - for (String key : keys) { - ModificationResult modificationResult = this.txResult.getModificationResult(key); - - this.observerHandler.add(key, modificationResult. getCreated()); - this.observerHandler.update(key, modificationResult. getUpdated()); - this.observerHandler.remove(key, modificationResult. getDeleted()); - } - } } private void handleRollback(long start) { @@ -324,4 +337,48 @@ public abstract class AbstractTransaction implements StrolchTransaction { throw new StrolchPersistenceException( "Strolch Transaction for realm " + getRealmName() + " failed due to " + e.getMessage(), e); //$NON-NLS-1$ } + + private void updateObservers() { + if (!this.suppressUpdates && this.observerHandler != null) { + + Set keys = this.txResult.getKeys(); + for (String key : keys) { + ModificationResult modificationResult = this.txResult.getModificationResult(key); + + this.observerHandler.add(key, modificationResult. getCreated()); + this.observerHandler.update(key, modificationResult. getUpdated()); + this.observerHandler.remove(key, modificationResult. getDeleted()); + } + } + } + + /** + * Calls {@link Command#validate()} on all registered command. This is done before we perform any commands and thus + * no rollback needs be done due to invalid input for a command + */ + private void validateCommands() { + for (Command command : this.commands) { + command.validate(); + } + } + + /** + * Calls {@link Command#doCommand()} on all registered commands. This is done after the commands have been validated + * so chance of a runtime exception should be small + */ + private void doCommands() { + for (Command command : this.commands) { + command.doCommand(); + } + } + + /** + * Calls {@link Command#undo()} on all registered commands. This is done when an exception is caught while + * performing the commands + */ + private void undoCommands() { + for (Command command : this.commands) { + command.undo(); + } + } } diff --git a/src/main/java/li/strolch/persistence/api/StrolchTransaction.java b/src/main/java/li/strolch/persistence/api/StrolchTransaction.java index 74686c2e1..c9feb8c67 100644 --- a/src/main/java/li/strolch/persistence/api/StrolchTransaction.java +++ b/src/main/java/li/strolch/persistence/api/StrolchTransaction.java @@ -29,6 +29,7 @@ import li.strolch.model.StrolchRootElement; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.OrderQuery; import li.strolch.model.query.ResourceQuery; +import li.strolch.service.api.Command; public interface StrolchTransaction extends AutoCloseable { @@ -61,6 +62,8 @@ public interface StrolchTransaction extends AutoCloseable { public void lock(T element); + public void addCommand(Command command); + public List doQuery(OrderQuery query); public List doQuery(ResourceQuery query); diff --git a/src/main/java/li/strolch/service/api/Command.java b/src/main/java/li/strolch/service/api/Command.java index cdea42493..a124b0686 100644 --- a/src/main/java/li/strolch/service/api/Command.java +++ b/src/main/java/li/strolch/service/api/Command.java @@ -73,6 +73,8 @@ public abstract class Command implements Restrictable { return this.getClass().getName(); } + public abstract void validate(); + public abstract void doCommand(); public abstract void undo();