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:
commit
1736435864
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue