Idempotent.java
package io.github.jonloucks.concurrency.api;
import io.github.jonloucks.contracts.api.AutoClose;
import io.github.jonloucks.contracts.api.AutoOpen;
import io.github.jonloucks.contracts.api.Contracts;
import static io.github.jonloucks.contracts.api.Checks.contractsCheck;
import static io.github.jonloucks.contracts.api.Checks.nullCheck;
/**
* Idempotent state machine states
*/
public enum Idempotent implements StateMachine.Rule<Idempotent> {
/**
* Initial state
* OPENABLE can transition to OPENED, OPENING, CLOSED, or DESTROYED.
*/
OPENABLE {
@Override
public boolean canTransition(String event, Idempotent goal) {
return goal == OPENED || goal == OPENING || goal == CLOSED || goal == DESTROYED;
}
},
/**
* For use cases where the open can take some time or reentrancy calls while initializing.
* Transitioning to OPENING and then OPENED will allow requests to not be rejected.
* OPENING can transition to OPENED, CLOSED, OPENABLE, DESTROYED.
*/
OPENING {
@Override
public boolean canTransition(String event, Idempotent goal) {
return goal == OPENED || goal == CLOSED || goal == OPENABLE || goal == DESTROYED;
}
@Override
public boolean isRejecting() {
return false;
}
},
/**
* Resource, service is open to actions
* OPEN can transition to CLOSING or CLOSED.
*/
OPENED {
@Override
public boolean canTransition(String event, Idempotent goal) {
return goal == CLOSING || goal == CLOSED;
}
@Override
public boolean isRejecting() {
return false;
}
},
/**
* A state of closing or shutting down.
* Note: isRejecting returns false during while close, but implementation can decide
* if a new action will be processed.
* CLOSING can transition to CLOSED or DESTROYED
*/
CLOSING {
@Override
public boolean canTransition(String event, Idempotent goal) {
return goal == CLOSED || goal == DESTROYED;
}
},
/**
* A state that all new actions are invalid
* Implementations can decide to ignore or throw exception, but never be processed.
* CLOSED can transition to OPENABLE or DESTROYED
*/
CLOSED {
@Override
public boolean canTransition(String event, Idempotent goal) {
return goal == OPENABLE || goal == DESTROYED;
}
},
/**
* Represents a permanent end state.
* Implementations can decide to ignore or throw exception, but never be processed.
* DESTROYED can not transition
*/
DESTROYED;
@Override
public boolean canTransition(String event, Idempotent goal) {
return false;
}
/**
* Determines if new requests/action should be rejected
*
* @return true if requests should be rejected.
* How they are rejected is an implementation decision.
* A request could be ignored, cause an exception, or be processed differently
*/
public boolean isRejecting() {
return true;
}
/**
* Create a StateMachine for Idempotency
* @param contracts the contracts for getting dependencies
* @return the new StateMachine
* @throws IllegalArgumentException if contracts is null
*/
public static StateMachine<Idempotent> createStateMachine(Contracts contracts) {
final Contracts validContracts = contractsCheck(contracts);
final StateMachineFactory factory = validContracts.claim(StateMachineFactory.CONTRACT);
return factory.create( b -> {
b.initial(OPENABLE);
for (Idempotent idempotent : Idempotent.values()) {
b.state(idempotent);
b.rule(idempotent, idempotent);
}
});
}
/**
* Assist with an idempotent close
*
* @param machine the StateMachine
* @param close the close
*/
public static void withClose(StateMachine<Idempotent> machine, AutoClose close) {
final StateMachine<Idempotent> validMachine = nullCheck(machine, "State machine must be present.");
final AutoClose validClose = nullCheck(close, "Close must be present.");
validMachine.transition(b -> b
.event("close")
.successState(Idempotent.CLOSED)
.successValue(() -> validClose.close())
.failedValue(() -> Void.TYPE)
.errorValue(() -> Void.TYPE));
}
/**
* Assist with an idempotent close
* @param machine the state machine
* @param open the open
* @return the AutoClose
*/
public static AutoClose withOpen(StateMachine<Idempotent> machine, AutoOpen open) {
final StateMachine<Idempotent> validMachine = nullCheck(machine, "State machine must be present.");
final AutoOpen validOpen = nullCheck(open, "Open must be present.");
return validMachine.transition(b -> b
.event("open")
.successState(Idempotent.OPENED)
.successValue(validOpen::open)
.failedValue(() -> AutoClose.NONE)
);
}
}