[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:
parent
1736435864
commit
85b82f6628
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue