[New] Added TransactionCloseStrategy.DO_NOTHING

- added a new closing strategy which is now the default closing
strategy. This effectively makes a new transaction read only
This commit is contained in:
Robert von Burg 2015-02-06 21:35:38 +01:00
parent 1736435864
commit 85b82f6628
5 changed files with 365 additions and 28 deletions

View File

@ -115,34 +115,26 @@ public abstract class AbstractTransaction implements StrolchTransaction {
this.commands = new ArrayList<>();
this.flushedCommands = new ArrayList<>();
this.lockedElements = new HashSet<>();
this.closeStrategy = TransactionCloseStrategy.ROLLBACK;
this.closeStrategy = TransactionCloseStrategy.DO_NOTHING;
this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), new Date());
this.txResult.setState(TransactionState.OPEN);
}
@Override
public boolean isOpen() {
return this.txResult.getState() == TransactionState.OPEN;
public TransactionState getState() {
return this.txResult.getState();
}
@Override
public boolean isRollingBack() {
return this.txResult.getState() == TransactionState.ROLLING_BACK;
return this.txResult.getState().isRollingBack();
}
@Override
public boolean isCommitting() {
return this.txResult.getState() == TransactionState.COMMITTING;
return this.txResult.getState().isCommitting();
}
@Override
public boolean isCommitted() {
return this.txResult.getState() == TransactionState.COMMITTED;
}
@Override
public boolean isRolledBack() {
return this.txResult.getState() == TransactionState.ROLLED_BACK;
public boolean isClosing() {
return this.txResult.getState().isClosing();
}
@Override
@ -159,6 +151,11 @@ public abstract class AbstractTransaction implements StrolchTransaction {
return certificate;
}
@Override
public TransactionCloseStrategy getCloseStrategy() {
return this.closeStrategy;
}
private void setCloseStrategy(TransactionCloseStrategy closeStrategy) {
this.closeStrategy = closeStrategy;
}
@ -168,6 +165,11 @@ public abstract class AbstractTransaction implements StrolchTransaction {
this.closeStrategy.close(this);
}
@Override
public void doNothingOnClose() {
setCloseStrategy(TransactionCloseStrategy.DO_NOTHING);
}
@Override
public void commitOnClose() {
setCloseStrategy(TransactionCloseStrategy.COMMIT);
@ -551,19 +553,80 @@ public abstract class AbstractTransaction implements StrolchTransaction {
}
}
@Override
public void autoCloseableDoNothing() throws StrolchTransactionException {
long start = System.nanoTime();
try {
this.txResult.setState(TransactionState.CLOSING);
if (!this.commands.isEmpty()) {
String msg = "Current close strategy {0} is readonly and thus does not support doing commands!";
msg = MessageFormat.format(msg, this.closeStrategy);
throw fail(msg);
}
long auditTrailDuration = writeAuditTrail();
// rollback and release any resources
rollback(this.txResult);
handleDoNothing(start, auditTrailDuration);
this.txResult.setState(TransactionState.CLOSED);
} catch (Exception e) {
handleFailure(start, e);
this.txResult.setState(TransactionState.FAILED);
} finally {
releaseElementLocks();
}
}
protected abstract void writeChanges(TransactionResult txResult) throws Exception;
protected abstract void rollback(TransactionResult txResult) throws Exception;
protected abstract void commit() throws Exception;
private void handleDoNothing(long start, long auditTrailDuration) {
long end = System.nanoTime();
long txDuration = end - this.txResult.getStartNanos();
long closeDuration = end - start;
this.txResult.setTxDuration(txDuration);
this.txResult.setCloseDuration(closeDuration);
StringBuilder sb = new StringBuilder();
sb.append("TX user=");
sb.append(this.certificate.getUsername());
sb.append(", realm="); //$NON-NLS-1$
sb.append(getRealmName());
sb.append(", took="); //$NON-NLS-1$
sb.append(StringHelper.formatNanoDuration(txDuration));
sb.append(", action=");
sb.append(this.action);
if (closeDuration >= 100000000L) {
sb.append(", close="); //$NON-NLS-1$
sb.append(StringHelper.formatNanoDuration(closeDuration));
}
if (isAuditTrailEnabled() && auditTrailDuration >= 100000000L) {
sb.append(", auditTrail="); //$NON-NLS-1$
sb.append(StringHelper.formatNanoDuration(auditTrailDuration));
}
logger.info(sb.toString());
}
private void handleCommit(long start, long auditTrailDuration, long observerUpdateDuration) {
long end = System.nanoTime();
long txDuration = end - this.txResult.getStartNanos();
long closeDuration = end - start;
this.txResult.setState(TransactionState.COMMITTED);
this.txResult.setTxDuration(txDuration);
this.txResult.setCloseDuration(closeDuration);
@ -603,7 +666,6 @@ public abstract class AbstractTransaction implements StrolchTransaction {
long txDuration = end - this.txResult.getStartNanos();
long closeDuration = end - start;
this.txResult.setState(TransactionState.ROLLED_BACK);
this.txResult.setTxDuration(txDuration);
this.txResult.setCloseDuration(closeDuration);

View File

@ -132,6 +132,20 @@ public interface StrolchTransaction extends AutoCloseable {
*/
public PersistenceHandler getPersistenceHandler();
/**
* Returns the currently set {@link TransactionCloseStrategy}
*
* @return the currently set {@link TransactionCloseStrategy}
*/
public TransactionCloseStrategy getCloseStrategy();
/**
* DO NOT CALL THIS METHOD. If the currently set close strategy is {@link TransactionCloseStrategy#DO_NOTHING}, then
* when the transaction is closed, this method is called no changes to the model is performed but locks on objects
* are released and any other resources are released
*/
public void autoCloseableDoNothing() throws StrolchTransactionException;
/**
* DO NOT CALL THIS METHOD. If the currently set close strategy is {@link TransactionCloseStrategy#COMMIT}, then
* when the transaction is closed, this method is called and all registered {@link Command} are performed, locks on
@ -166,6 +180,11 @@ public interface StrolchTransaction extends AutoCloseable {
@Override
public void close() throws StrolchTransactionException;
/**
* Sets the {@link TransactionCloseStrategy} to {@link TransactionCloseStrategy#DO_NOTHING}
*/
public void doNothingOnClose();
/**
* Sets the {@link TransactionCloseStrategy} to {@link TransactionCloseStrategy#COMMIT}
*/
@ -198,29 +217,26 @@ public interface StrolchTransaction extends AutoCloseable {
public void flush();
/**
* @return true if the transaction is still open, i.e. not being closed or rolling back, committing, etc.
* @return the current state of the transaction
*
* @see TransactionState
*/
public boolean isOpen();
public TransactionState getState();
/**
* @return true if the transaction is in the process of rolling back all changes
* @return if the current state of the {@link StrolchTransaction} is {@link TransactionState#ROLLING_BACK}
*/
public boolean isRollingBack();
/**
* @return true if the transaction is in the process of committing the changes
* @return if the current state of the {@link StrolchTransaction} is {@link TransactionState#COMMITTING}
*/
public boolean isCommitting();
/**
* @return if the transaction has committed all changes
* @return if the current state of the {@link StrolchTransaction} is {@link TransactionState#CLOSING}
*/
public boolean isCommitted();
/**
* @return if the transaction has rolled back all changes
*/
public boolean isRolledBack();
public boolean isClosing();
/**
* If the given argument is true, then no observer updates are performed

View File

@ -17,16 +17,68 @@ package li.strolch.persistence.api;
import li.strolch.exception.StrolchException;
/**
* Defines what happens when a {@link StrolchTransaction} is closed. Strolch transactions are auto closeable which means
* when used in a try-with-resource block, then they are automatically closed. Depending on the use case, these
* transactions might write something to the DB, or only read the model, or even have to roll back, this is controlled
* by using these enums.
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public enum TransactionCloseStrategy {
/**
* <p>
* This is for read only transactions. If this strategy is used, then no changes will be written to the model. Use
* {@link StrolchTransaction#doNothingOnClose()} to simply close the transaction, releasing any resources
* </p>
*
* <p>
* <b>Note:</b> When using this strategy, then the transaction will throw exceptions if you try to add commands to
* the transaction.
* </p>
*/
DO_NOTHING() {
@Override
public boolean isReadonly() {
return true;
}
@Override
public void close(StrolchTransaction tx) throws StrolchException {
tx.autoCloseableDoNothing();
}
},
/**
* The main close strategy type where changes are written to the model. Use
* {@link StrolchTransaction#commitOnClose()} to commit any changes i.e. commands added to the transaction
*/
COMMIT() {
@Override
public boolean isReadonly() {
return false;
}
@Override
public void close(StrolchTransaction tx) throws StrolchException {
tx.autoCloseableCommit();
}
},
/**
* In exceptional cases one might not want any changes to be written to the model, thus calling
* {@link StrolchTransaction#rollbackOnClose()} will have the transaction roll back all changes
*/
ROLLBACK() {
@Override
public boolean isReadonly() {
return true;
}
@Override
public void close(StrolchTransaction tx) throws StrolchException {
tx.autoCloseableRollback();
@ -34,4 +86,6 @@ public enum TransactionCloseStrategy {
};
public abstract void close(StrolchTransaction tx) throws StrolchException;
public abstract boolean isReadonly();
}

View File

@ -15,11 +15,39 @@
*/
package li.strolch.persistence.api;
/**
* The different states of a {@link StrolchTransaction}
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public enum TransactionState {
OPEN, //
CLOSING, //
COMMITTING, //
ROLLING_BACK, //
CLOSED, //
COMMITTED, //
ROLLED_BACK,
FAILED;
/**
* @return true if this is {@link #ROLLING_BACK}
*/
public boolean isRollingBack() {
return this == ROLLING_BACK;
}
/**
* @return true if this is {@link #COMMITTING}
*/
public boolean isCommitting() {
return this == COMMITTING;
}
/**
* @return true if this is {@link #CLOSING}
*/
public boolean isClosing() {
return this == CLOSING;
}
}

View File

@ -0,0 +1,177 @@
package li.strolch.service;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import li.strolch.command.AddResourceCommand;
import li.strolch.model.ModelGenerator;
import li.strolch.model.Order;
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 TxTest extends AbstractRealmServiceTest {
@Test
public void shouldCommit() {
runServiceInAllRealmTypes(CommitService.class, new ServiceArgument());
}
@Test
public void shouldRollback() {
runServiceInAllRealmTypes(RollbackService.class, new ServiceArgument());
}
@Test
public void shouldDoNothing() {
runServiceInAllRealmTypes(ReadonlyService.class, new ServiceArgument());
}
@Test
public void shouldNotAllowCommandsOnDoNothibg() {
runServiceInAllRealmTypes(ReadonlyFailService.class, new ServiceArgument());
}
public static class CommitService 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));
tx.commitOnClose();
}
// now make sure the new resource exists
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 RollbackService 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));
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 find expected resource with id " + id);
}
}
return ServiceResult.success();
}
}
public static class ReadonlyService 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 {
try (StrolchTransaction tx = openTx(arg.realm)) {
Resource yellowBall = tx.getResourceBy("Ball", "yellow");
assertNotNull("Expected to find the yellow ball", yellowBall);
Order myCarOrder = tx.getOrderBy("ProductionOrder", "myCarOrder");
assertNotNull("Expected to find the my car order", myCarOrder);
}
return ServiceResult.success();
}
}
public static class ReadonlyFailService 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);
boolean txFailed = false;
try (StrolchTransaction tx = openTx(arg.realm)) {
AddResourceCommand addResCmd = new AddResourceCommand(getContainer(), tx);
addResCmd.setResource(resource);
tx.addCommand(addResCmd);
} catch (Exception e) {
// expected
txFailed = true;
}
assertTrue("TX should have failed!", txFailed);
return ServiceResult.success();
}
}
}