Table of Contents

Events - processing of events

TODO: Expand the introduction

An event mechanism has been designed to make extending and overlapping of the CzechIdM core functionality within any module possible.

The event (EntityEvent) is published via EntityEventManager from different places in the application (⇒ hook). A number of processors can react to the event (AbstractEntityEventProcessor) in a defined order (number ⇒ the smaller it is, the sooner the processor is run). Processors run synchronously at default and in one transaction (see next section). Processors with the same order will be run in a random order (OrderComparator) - it's good practice to design and set different processor's order (think about it in design). Instead of the annotation @Order, the method getOrder needs to be overloaded (see the example). Event content could be any Serializable object, but AbstractDto descendant is preferred - see original source lifecycle feature. Event content is required, event without content couldn't exist.

Event lifecycle

Supported events

Supported events for individual entities:

A page has been created directly in the application on the module page for an overview of all entity types and event types migrating through event processing. All the registered processors including the configuration are listed there:

The default order for listeners (- ⇒ before, + ⇒ after):

Event types

Event is published with content and specific event type (e.g. CREATE, PASSWORD). Processors can be register by content type and event type - e.g. process event with IdmIdentityDto content and event type CREATE ⇒ other event contents and other event types will not be processed by this processor.

Event types has to be compared by their string representation, NOT by instance. Concrete event types e.q. IdentityEventType are used for documentation reason only - which domain type supports which event. Event types can be added in different modules with different type, but processor can react across all module (⇒ is registered to string event type representation eventType.name()).

...
EntityEvent<IdmIdentityDto> event = new CoreEvent<IdmIdentityDto>(CoreEventType.CREATE, identity);
if (IdentityEventType.CREATE.name() == event.getType().name()) {
 // do something
}
...

Basic interfaces

Basic classes

Transactions

Transactional processing is controlled before the event publishing itself - the whole processing now takes place in a transaction and all processors run synchronously. In case of an error in any processor, the whole transaction is rolled back, which has some advantages:

and disadvantages as well:

Predefined processors order

Processor configuration

Processors can be configured through Configurable interface by standard application configuration.

Implemented processors

Automatic roles processors

##
## approve create automatic role
idm.sec.core.processor.role-tree-node-create-approve-processor.enabled=true
# wf definition 
idm.sec.core.processor.role-tree-node-create-approve-processor.wf=approve-create-automatic-role
##
## approve delete automatic role
idm.sec.core.processor.role-tree-node-delete-approve-processor.enabled=true
# wf definition 
idm.sec.core.processor.role-tree-node-delete-approve-processor.wf=approve-delete-automatic-role

Notification on change monitored Identity fields

# Identity changed monitored fields - Check if defined fields on identity was changed. If yes, then send notification.
# Default is disabled
idm.sec.core.processor.identity-monitored-fields-processor.enabled=false
# Monitored fields on change (for Identity, extended attributes are not supported)
idm.sec.core.processor.identity-monitored-fields-processor.monitoredFields=firstName, lastName
# Notification will be send to all identities with this role
idm.sec.core.processor.identity-monitored-fields-processor.recipientsRole=superAdminRole

Change user permissions workflow

# Default is enabled
idm.sec.core.processor.role-request-approval-processor.enabled=true
# Workflow process for change permissions (as default is "approve-identity-change-permissions")
idm.sec.core.processor.role-request-approval-processor.wf=approve-identity-change-permissions

Change user permissions workflow - Approval by the helpdesk department

# The role can be changed in the application configuration "idm.sec.core.wf.approval.helpdesk.role", the default setting is Helpdesk.
idm.sec.core.wf.approval.helpdesk.role=Helpdesk
# Default is disabled
idm.sec.core.wf.approval.helpdesk.enabled=false

Change user permissions workflow - Approval by the manager

# Default is disabled
idm.sec.core.wf.approval.manager.enabled=false

Change user permissions workflow - Approval by the user administration department

# The role can be changed in the application configuration "idm.sec.core.wf.approval.usermanager.role", the default setting is Usermanager.
idm.sec.core.wf.approval.usermanager.role=Usermanager
# Default is disabled
idm.sec.core.wf.approval.usermanager.enabled=false

Example

If I want to get myself hooked after deleting the identity, I should implement a processor to the event type IdentityEventType.DELETE with an order number higher than 0:

@Enabled(ExampleModuleDescriptor.MODULE_ID)
@Component("exampleLogIdentityDeleteProcessor")
@Description("Log after identity is deleted")
public class LogIdentityDeleteProcessor extends AbstractEntityEventProcessor<IdmIdentityDto> {
 
	/**
	 * Processor's identifier - has to be unique by module
	 */
	public static final String PROCESSOR_NAME = "log-identity-delete-processor";
	private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory
			.getLogger(LogIdentityDeleteProcessor.class);
 
	public LogIdentityDeleteProcessor() {
		// processing identity DELETE event only
		super(IdentityEventType.DELETE);
	}
 
	@Override
	public String getName() {
		// processor's identifier - has to be unique by module
		return PROCESSOR_NAME;
	}
 
	@Override
	public EventResult<IdmIdentityDto> process(EntityEvent<IdmIdentityDto> event) {
		// event content - identity
		IdmIdentityDto deletedIdentity = event.getContent();
		// log
		LOG.info("Identity [{},{}] was deleted.", deletedIdentity.getUsername(), deletedIdentity.getId());
		// result
		return new DefaultEventResult<>(event, this);
	}
 
	@Override
	public int getOrder() {
		// right after identity delete
		return CoreEvent.DEFAULT_ORDER + 1;
	}
}