/fsmw

A stateless finite state machine definition, with workflow support

Primary LanguageJavaApache License 2.0Apache-2.0

fsmw

Stateless Finite State Machine and Workflow

A stateless finite state machine definition, with workflow support, to develop a easy to read declaration of a Workflow (or state machine only).

		WorkflowConfig<STATUS, EVENT, ROLE> fsmw = new WorkflowConfig<>("TestDocWorkflowFSM");

		// Only the USER can create a TestDoc
		fsmw.state(NULL).actors(USER)
				// when the TestDoc is saved goes to draft status
				.transition(SAVE, DRAFT).action(save());

		// At DRAFT status, only the USER can trigger actions
		fsmw.state(DRAFT).actors(USER)
				// he can VIEW the testDoc (as sample, a recovery action can be executed at view self transition)
				.self(VIEW).action(recovery()).state()
				// he can save (so, he can edit), and if done, testDoc is saved
				.self(SAVE).action(save()).state()
				// if he FORWARD the testDoc, a transaction implying save, change status and admin notification will be triggered
				.transition(FORWARD, REVIEW).action(transactionalAction(save(), changeStatus(), adminNotification())).state()
				// he can deny the testDoc without sending it to revision
				.transition(DENY, DENIED).action(transactionalAction(save(), changeStatus()));

		// At REVISION status
		fsmw.state(REVIEW)
				// all can see the doc
				.self(VIEW).action(recovery()).state()
				// but only the ADMIN can edit
				.self(SAVE).actors(ADMIN).action(save()).state()
				// and only the ADMIN can FORWARD it, marking it as ACCEPTED
				.transition(FORWARD, ACCEPTED).actors(ADMIN).action(transactionalAction(save(), changeStatus())).state()
				// and only the ADMIN can DENY it, marking it as DENIED, and notifying user
				.transition(DENY, DENIED).actors(ADMIN).action(transactionalAction(save(), changeStatus(), userNotification())).state();

		// At ACCEPTED status
		fsmw.state(ACCEPTED)
				// only can be viewd
				.self(VIEW).action(recovery());

		// At DENIED status only the user
		fsmw.state(DENIED).actors(USER)
				// can view the testDoc
				.self(VIEW).action(recovery()).state()
				// and can do a CLAIM, copying the testDoc on a new instance that will start the workflow again
				.self(CLAIM).action(copyOnReclamation());

It also contains some action helpers for workflow definition (conditional and sequence actions) and boolean decisors (and, or, not)

A test specification is also readable

		TestDoc testDoc = new TestDoc();

		// get a workflow instance for the testDoc
		Workflow<STATUS, EVENT, ROLE> workflow = TestDocWorkflow.WORKFLOW(testDoc);

		// admin cannot save
		// but user can save and status will go to draft
		Assert.assertFalse(workflow.canFire(create(ROLE.ADMIN, EVENT.SAVE, testDoc)));
		Assert.assertTrue(workflow.canFire(create(ROLE.USER, EVENT.SAVE, testDoc)));

		workflow.fire(create(ROLE.USER, EVENT.SAVE, testDoc));
		Assert.assertEquals(STATUS.DRAFT, workflow.getState());

		// at draft, only user can save
		Assert.assertTrue(workflow.canFire(create(ROLE.USER, EVENT.SAVE, testDoc)));
		Assert.assertFalse(workflow.canFire(create(ROLE.ADMIN, EVENT.SAVE, testDoc)));
		workflow.fire(create(ROLE.USER, EVENT.SAVE, testDoc));

		// and user can pass it to review if he forwards the testDoc
		workflow.fire(create(ROLE.USER, EVENT.FORWARD, testDoc));
		Assert.assertEquals(STATUS.REVIEW, workflow.getState());

		// at review, only admin can forward the testDoc
		Assert.assertTrue(workflow.canFire(create(ROLE.ADMIN, EVENT.FORWARD, testDoc)));
		Assert.assertFalse(workflow.canFire(create(ROLE.USER, EVENT.FORWARD, testDoc)));
		Assert.assertTrue(workflow.canFire(create(ROLE.USER, EVENT.VIEW, testDoc)));

		// and after this forward, testDoc goes to accepted
		workflow.fire(create(ROLE.ADMIN, EVENT.FORWARD, testDoc));
		Assert.assertEquals(STATUS.ACCEPTED, workflow.getState());