Merge branch 'feature/tx-flush' into develop

Conflicts:
	li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java
This commit is contained in:
Robert von Burg 2015-02-06 09:20:34 +01:00
commit 1736435864
4 changed files with 245 additions and 6 deletions

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.Set; import java.util.Set;
import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.AuditTrail;
@ -88,6 +89,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
private TransactionResult txResult; private TransactionResult txResult;
private List<Command> commands; private List<Command> commands;
private List<Command> flushedCommands;
private Set<StrolchRootElement> lockedElements; private Set<StrolchRootElement> lockedElements;
private AuditingOrderMap orderMap; private AuditingOrderMap orderMap;
@ -111,6 +113,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
this.certificate = certificate; this.certificate = certificate;
this.commands = new ArrayList<>(); this.commands = new ArrayList<>();
this.flushedCommands = new ArrayList<>();
this.lockedElements = new HashSet<>(); this.lockedElements = new HashSet<>();
this.closeStrategy = TransactionCloseStrategy.ROLLBACK; this.closeStrategy = TransactionCloseStrategy.ROLLBACK;
this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), new Date()); this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), new Date());
@ -463,12 +466,24 @@ public abstract class AbstractTransaction implements StrolchTransaction {
return getResourceMap().getBy(this, refP, assertExists); return getResourceMap().getBy(this, refP, assertExists);
} }
@Override
public void flush() {
try {
validateCommands();
doCommands();
writeChanges(this.txResult);
} catch (Exception e) {
this.closeStrategy = TransactionCloseStrategy.ROLLBACK;
String msg = "Strolch Transaction for realm {0} failed due to {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, getRealmName(), e.getMessage());
throw new StrolchTransactionException(msg, e);
}
}
@Override @Override
public void autoCloseableCommit() { public void autoCloseableCommit() {
long start = System.nanoTime(); long start = System.nanoTime();
if (logger.isDebugEnabled()) {
logger.info(MessageFormat.format("Committing TX for realm {0}...", getRealmName())); //$NON-NLS-1$
}
try { try {
this.txResult.setState(TransactionState.COMMITTING); this.txResult.setState(TransactionState.COMMITTING);
@ -485,6 +500,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
handleCommit(start, auditTrailDuration, updateObserversDuration); handleCommit(start, auditTrailDuration, updateObserversDuration);
this.txResult.setState(TransactionState.COMMITTED);
} catch (Exception e) { } catch (Exception e) {
this.txResult.setState(TransactionState.ROLLING_BACK); this.txResult.setState(TransactionState.ROLLING_BACK);
try { try {
@ -505,6 +522,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
handleFailure(start, e); handleFailure(start, e);
} }
this.txResult.setState(TransactionState.FAILED);
String msg = "Strolch Transaction for realm {0} failed due to {1}"; //$NON-NLS-1$ String msg = "Strolch Transaction for realm {0} failed due to {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, getRealmName(), e.getMessage()); msg = MessageFormat.format(msg, getRealmName(), e.getMessage());
throw new StrolchTransactionException(msg, e); throw new StrolchTransactionException(msg, e);
@ -519,11 +538,14 @@ public abstract class AbstractTransaction implements StrolchTransaction {
long start = System.nanoTime(); long start = System.nanoTime();
logger.warn(MessageFormat.format("Rolling back TX for realm {0}...", getRealmName())); //$NON-NLS-1$ logger.warn(MessageFormat.format("Rolling back TX for realm {0}...", getRealmName())); //$NON-NLS-1$
try { try {
this.txResult.setState(TransactionState.ROLLING_BACK);
undoCommands(); undoCommands();
rollback(this.txResult); rollback(this.txResult);
handleRollback(start); handleRollback(start);
this.txResult.setState(TransactionState.ROLLED_BACK);
} catch (Exception e) { } catch (Exception e) {
handleFailure(start, e); handleFailure(start, e);
this.txResult.setState(TransactionState.FAILED);
} finally { } finally {
releaseElementLocks(); releaseElementLocks();
} }
@ -759,8 +781,12 @@ public abstract class AbstractTransaction implements StrolchTransaction {
* so chance of a runtime exception should be small * so chance of a runtime exception should be small
*/ */
private void doCommands() { private void doCommands() {
for (Command command : this.commands) { ListIterator<Command> iter = this.commands.listIterator();
while (iter.hasNext()) {
Command command = iter.next();
command.doCommand(); command.doCommand();
this.flushedCommands.add(command);
iter.remove();
} }
} }
@ -769,8 +795,11 @@ public abstract class AbstractTransaction implements StrolchTransaction {
* performing the commands * performing the commands
*/ */
private void undoCommands() { private void undoCommands() {
for (Command command : this.commands) { ListIterator<Command> iter = this.flushedCommands.listIterator(this.flushedCommands.size());
while (iter.hasPrevious()) {
Command command = iter.previous();
command.undo(); command.undo();
iter.remove();
} }
} }
} }

View File

@ -17,7 +17,6 @@ package li.strolch.persistence.api;
import java.util.List; import java.util.List;
import ch.eitchnet.privilege.model.Certificate;
import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.AuditTrail;
import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.OrderMap;
import li.strolch.agent.api.ResourceMap; import li.strolch.agent.api.ResourceMap;
@ -46,6 +45,7 @@ import li.strolch.model.query.OrderQuery;
import li.strolch.model.query.ResourceQuery; import li.strolch.model.query.ResourceQuery;
import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.StrolchConstants;
import li.strolch.service.api.Command; import li.strolch.service.api.Command;
import ch.eitchnet.privilege.model.Certificate;
/** /**
* <p> * <p>
@ -186,6 +186,17 @@ public interface StrolchTransaction extends AutoCloseable {
*/ */
public StrolchTransactionException fail(String exceptionMessage); public StrolchTransactionException fail(String exceptionMessage);
/**
* <p>
* Performs all registered commands
* </p>
*
* <p>
* This method does not release any locks, nor does it notify any observers
* </p>
*/
public void flush();
/** /**
* @return true if the transaction is still open, i.e. not being closed or rolling back, committing, etc. * @return true if the transaction is still open, i.e. not being closed or rolling back, committing, etc.
*/ */

View File

@ -385,9 +385,11 @@ public abstract class PostgresqlDao<T extends StrolchElement> implements Strolch
} }
void commit(TransactionResult txResult) { void commit(TransactionResult txResult) {
// even though we support rollback we can clear the commands here even if we performed them because the DB transaction will be rolled back
for (DaoCommand command : this.commands) { for (DaoCommand command : this.commands) {
command.doComand(txResult); command.doComand(txResult);
} }
this.commands.clear();
} }
void rollback() { void rollback() {

View File

@ -0,0 +1,197 @@
package li.strolch.service;
import li.strolch.command.AddResourceCommand;
import li.strolch.command.RemoveResourceCommand;
import li.strolch.command.UpdateResourceCommand;
import li.strolch.model.ModelGenerator;
import li.strolch.model.Resource;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceArgument;
import li.strolch.service.api.ServiceResult;
import li.strolch.service.test.AbstractRealmServiceTest;
import org.junit.Test;
import ch.eitchnet.utils.dbc.DBC;
public class FlushTxTest extends AbstractRealmServiceTest {
@Test
public void shouldFlushSuccessfully1() {
runServiceInAllRealmTypes(FlushingCommandsService1.class, new ServiceArgument());
}
@Test
public void shouldFlushSuccessfully2() {
runServiceInAllRealmTypes(FlushingCommandsService2.class, new ServiceArgument());
}
@Test
public void shouldRollbackSuccessfully() {
runServiceInAllRealmTypes(RollbackAfterFlushCommandsService.class, new ServiceArgument());
}
public static class FlushingCommandsService1 extends AbstractService<ServiceArgument, ServiceResult> {
private static final long serialVersionUID = 1L;
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
protected ServiceResult internalDoService(ServiceArgument arg) throws Exception {
String id = "flushSuccessfully";
Resource resource = ModelGenerator.createResource(id, id, id);
try (StrolchTransaction tx = openTx(arg.realm)) {
DBC.PRE.assertNull("Did not expect resource with id " + id, tx.getResourceBy(id, id));
AddResourceCommand addResCmd = new AddResourceCommand(getContainer(), tx);
addResCmd.setResource(resource);
tx.addCommand(addResCmd);
tx.flush();
DBC.PRE.assertNotNull("Expected resource with id " + id, tx.getResourceBy(id, id));
RemoveResourceCommand rmResCmd = new RemoveResourceCommand(getContainer(), tx);
rmResCmd.setResource(resource);
tx.addCommand(rmResCmd);
tx.flush();
DBC.PRE.assertNull("Expect to remove resource with id " + id, tx.getResourceBy(id, id));
tx.commitOnClose();
}
// now make sure the new resource does not exist
try (StrolchTransaction tx = openTx(arg.realm)) {
Resource res = tx.getResourceBy(id, id);
if (res != null) {
throw tx.fail("Did not expect resource with id " + id);
}
}
return ServiceResult.success();
}
}
public static class FlushingCommandsService2 extends AbstractService<ServiceArgument, ServiceResult> {
private static final long serialVersionUID = 1L;
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
protected ServiceResult internalDoService(ServiceArgument arg) throws Exception {
String id = "flushSuccessfully";
Resource resource = ModelGenerator.createResource(id, id, id);
try (StrolchTransaction tx = openTx(arg.realm)) {
DBC.PRE.assertNull("Did not expect resource with id " + id, tx.getResourceBy(id, id));
AddResourceCommand addResCmd = new AddResourceCommand(getContainer(), tx);
addResCmd.setResource(resource);
tx.addCommand(addResCmd);
tx.flush();
DBC.PRE.assertNotNull("Expected resource with id " + id, tx.getResourceBy(id, id));
Resource res = tx.getResourceBy(id, id);
UpdateResourceCommand updateResCmd = new UpdateResourceCommand(getContainer(), tx);
updateResCmd.setResource(res);
tx.addCommand(updateResCmd);
tx.commitOnClose();
}
// now make sure the new resource does exist
try (StrolchTransaction tx = openTx(arg.realm)) {
Resource res = tx.getResourceBy(id, id);
if (res == null) {
throw tx.fail("Did not find expected resource with id " + id);
}
}
return ServiceResult.success();
}
}
public static class RollbackAfterFlushCommandsService extends AbstractService<ServiceArgument, ServiceResult> {
private static final long serialVersionUID = 1L;
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
protected ServiceResult internalDoService(ServiceArgument arg) throws Exception {
String id = "flushSuccessfully2";
Resource resource = ModelGenerator.createResource(id, id, id);
try (StrolchTransaction tx = openTx(arg.realm)) {
DBC.PRE.assertNull("Did not expect resource with id " + id, tx.getResourceBy(id, id));
AddResourceCommand addResCmd = new AddResourceCommand(getContainer(), tx);
addResCmd.setResource(resource);
tx.addCommand(addResCmd);
tx.flush();
DBC.PRE.assertNotNull("Expected resource with id " + id, tx.getResourceBy(id, id));
// now force a rollback
tx.rollbackOnClose();
}
// now make sure the new resource does not exist
try (StrolchTransaction tx = openTx(arg.realm)) {
Resource res = tx.getResourceBy(id, id);
if (res != null) {
throw tx.fail("Did not expect resource with id after rolling back previous TX " + id);
}
}
// now do it over, but use throw
try (StrolchTransaction tx = openTx(arg.realm)) {
DBC.PRE.assertNull("Did not expect resource with id " + id, tx.getResourceBy(id, id));
AddResourceCommand addResCmd = new AddResourceCommand(getContainer(), tx);
addResCmd.setResource(resource);
tx.addCommand(addResCmd);
tx.flush();
DBC.PRE.assertNotNull("Expected resource with id " + id, tx.getResourceBy(id, id));
// now force a rollback
throw tx.fail("Oh snap, something went wrong!");
} catch (Exception e) {
// expected
}
// now make sure the new resource does not exist
try (StrolchTransaction tx = openTx(arg.realm)) {
Resource res = tx.getResourceBy(id, id);
if (res != null) {
throw tx.fail("Did not expect resource with id after rolling back previous TX " + id);
}
}
return ServiceResult.success();
}
}
}