Table of Contents

Events - processing of events

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

The event (EntityEvent) with type (EventType) 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 preffered - see original source lifecycle feature. Event content is required, event without content couldn't exist.

Event lifecycle

EntityEvent<IdmIdentityDto> event = new IdentityEvent(IdentityEventType.UPDATE, updateIdentity);
EventContext<IdmIdentityDto> context = entityEventManager.process(event);

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 (event.hasType(IdentityEventType.CREATE)) {
 // do something
}
...

Module can publish their own event types. Basic (core) event types

Different (and custom) event types can be used for different entities.

Entities with event support

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):

Basic interfaces

Basic classes

AbstractEntityEventProcessor

Use this super class, when creating new processor implementation (this class contains some boring parts).

Methods, which have to be implemented:

Methods, which could be implemented [optional]:

AsyncEntityEventProcessor

Use this super class, when creating new asynchronous processor implementation.

Methods, which could be implemented [optional]:

Transactions

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

and disadvantages as well:

Asynchronous event processing

CzechIdM 8.0.0 brings new feature - asynchronous event processing. New event type NOTIFY was added, all previous events (CREATE, UPDATE, DELETE, EAV_SAVE etc.) are still synchronous.

Asynchronous NOTIFY event is published for dtos:

As you can see, entity DELETE event is still synchronous.

Other entities will be added soon, when new asynchronous entity event processors will be implemented.

NOTIFY event type is processed asynchronously:

When asynchronous event is published, it's persisted into event queue (IdmEntityEvent). Internal scheduled task executes events from queue - all registered processors for event type NOTIFY is processed - the same behavior as standard event processing, processors are called in defined order synchronously by defult. Event (~entity) states (IdmEntityState) are persisted during event is processed (created / running / failed). Successfully processed events are deleted from queue by processor EntityEventDeleteExecutedProcessor. When exception occurs, event stays in queue with appropriate result code. Event agenda is available under APP_ADMIN permission on frontend from audit menu (shortcut tab can be added on all entity details e.g. see identity detail).

Features

Process event from queue

Events from queue are processed by event owner id - one event for one owner can be executed in the same time ⇒ we need to preserven event order by created date for one owner. Super owner id (EntityEventManager.EVENTPROPERTYSUPEROWNERID) can be used for setting custom event owner - this property will be resolved for evaluating running events for the same owner concurrency.

Events from queue can be deleted only (events without children can be deleted now from FE). Operation for retry failed events on truncate all events in queue will be developed in future.

Event priority

Before event is persisted into queue, then event priority is evaluated, priority types:

Events are processed from queue by internal scheduled tasks by priority. Events with HIGH priority will use 7 slots, events with NORMAL priority will use 3 slots ⇒ events will be processed 7 / 3, when internal scheduled task for processing events will be executed.

Priority can be set to event manually or registered processors can vote about event's priority - see AsyncEntityEventProcessor - the highest priority is used.

Execute date

Execute date can be set to event manually. Event with priority HIGH or NORMAL will be processed after given date. Can be used for events, which could be executed sometimes "in night".

Parent event

Event can be published by another event ~ event chain (tree) is persisted. For example, when contract is saved, then contract NOTIFY event is published. This event is processed by provisioning processors - but only NOTIFY event with contract's identity is published here only. Provisioning is physically executed in other processor, which processes identity NOTIFY event.

Event parameters

When asynchronous event is published, then event content (and previous ~ original content) and event parameters is persisted into queue. This persisted attributes are used, when event is resurrected from queue and executed. Attributes are available in registered asynchronous processors - evaluate modifications, skipping by event parameter value etc. can be implemented in processors business logic.

Remove duplicate events

When internal scheduled task for executing event from queue is processed, then duplicate events are removed. Duplicate event is event for the same owner with the same event type and properties. Older duplicate events are removed - the newest event is used. Events are processed by priority in bulk, default bulk size is 100 events ⇒ duplicates are removed only in this bulk (not configurable for now, see future development). Bulk size is designed this way, because events are processed by priority - event with HIGH priority should not wait too long for another bulk is begin to process. Remove duplicates should be redesigned from scratch - remove duplicates through whole queue.

Entity state

Persist event (~entity) state, when event is processed. State can be persisted manually, even without event processing. This state will be shown on entity detail soon (new frontend component).

Notification

Notification about registered asynchronous processors is prepared, when asynchronous event is published. Notification is send into topic core:event - uses console log by default and is send to currently logged identity - e.g. identity is saved, but provisioning will be executed asynchronously. Localization for asynchronous processors was added on frontend (see key acc:processor.identity-save-processor).

Configuration

Predefined processors order

Other orders can be found directly in application, see supported event types.

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

Hr processes processors

##
## HR process - enable identity's contract process. The processes is started
## for contracts that are both valid (meaning validFrom and validTill and disabled state) and
## not excluded.
idm.sec.core.processor.identity-contract-enable-processor.enabled=true
# wf definition 
idm.sec.core.processor.identity-contract-enable-processor.wf=hrEnableContract
##
## HR process - end or delete of identity's contract process. The processes is started
## for contracts that are not valid (meaning validFrom and validTill or disabled by state) and deleted. 
## If the processed contract was the last valid contract of the identity, the identity is disabled.
## Additionally all added roles, which were assigned to the ended contract, are removed by the process.
idm.sec.core.processor.identity-contract-end-processor.enabled=true
# wf definition 
idm.sec.core.processor.identity-contract-end-processor.wf=hrEndContract
##
## HR process - identity's contract exclusion. The processes is started for
## contracts that are both valid (meaning validFrom and validTill) and excluded.
## If the processed contract was the last valid contract of the identity, the identity is disabled.
idm.sec.core.processor.identity-contract-exclusion-processor.enabled=true
# wf definition 
idm.sec.core.processor.identity-contract-exclusion-processor.wf=hrContractExclusion

Provisioning after create, update or delete manually added guarantee for contract

## Provisioning identity after add or update IdmContractGuaranteeDto
idm.sec.acc.processor.contract-guarantee-save.enabled=true
##
## Provisioning identity before IdmContractGuaranteeDto will be removed
idm.sec.acc.processor.contract-guarantee-delete.enabled=true

LongRunningTaskEndProcessor

When some long running task ends, then END event is fired. This processor persists task's state.

LongRunningTaskExecuteDependentProcessor

When some long running task ends, then END event is fired. This processor executes scheduled long running tasks, which depends on currently ended scheduled task.

ReportGenerateProcessor

Processes GENERATE event type with RptReportDto content, order -1000. Generates output data for report by long running task.

## Enable / disable
idm.sec.rpt.processor.report-generate-processor.enabled=true

ReportGenerateEndProcessor

Processes GENERATE event type with RptReportDto content, order 0. Saves generated report metadata (binary data are stored as attachment).

## Enable / disable
idm.sec.rpt.processor.report-generate-end-processor.enabled=true

ReportGenerateEndSendNotificationProcessor

Processes GENERATE event type with RptReportDto content, order 1000. Sends notification after report is generated to report creator.

## Enable / disable
idm.sec.rpt.processor.report-generate-end-send-notification-processor.enabled=true

IdentitySetPasswordProcessor

Processes UPDATE event type with IdmIdentityDto content, order 200. When identity starts to be valid and has at least one account on target system, then new password is generated and changed on all identity's accounts ⇒ identity ha the same password in all accounts. Notification is send (see acc:newPasswordAllSystems template) to identity about new password on which accounts.

Identity is starting, when their state is changed from CREATED, NO_CONTRACT, FUTURE_CONTRACT to the VALID state.

## Enable / disable
idm.sec.acc.processor.identity-set-password-processor.enabled=true

EntityEventStartProcessor

Start execution of entity event.

## Enable / disable
idm.sec.core.processor.entity-event-start-processor.enabled=true

EntityEventExecuteProcessor

## Enable / disable
idm.sec.core.processor.entity-event-execute-processor.enabled=true

Execute entity event - resurrects entity event and process her - execute all registered processors.

EntityEventEndProcessor

End execution of entity event - persist state only.

## Enable / disable
idm.sec.core.processor.entity-event-end-processor.enabled=true

EntityEventDeleteExecutedProcessor

Delete successfully executed entity events.

## Enable / disable
idm.sec.core.processor.entity-event-delete-executed-processor.enabled=true

Example

Synchronous processor

If we want to get hooked after updating the identity, we should implement a processor to the event type IdentityEventType.UPDATE with an order number higher than 0:

@Enabled(ExampleModuleDescriptor.MODULE_ID)
@Component("exampleLogIdentityUpdateSyncProcessor")
@Description("Logs after identity is updated")
public class LogIdentityUpdateSyncProcessor
		extends CoreEventProcessor<IdmIdentityDto> 
		implements IdentityProcessor {
 
	/**
	 * Processor's identifier - has to be unique by module
	 */
	public static final String PROCESSOR_NAME = "log-identity-update-sync-processor";
	private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory
			.getLogger(LogIdentityUpdateSyncProcessor.class);
 
	public LogIdentityUpdateSyncProcessor() {
		// processing identity UPDATE event only
		super(IdentityEventType.UPDATE);
	}
 
	@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 updateddIdentity = event.getContent();
		// log
		LOG.info("Identity [{},{}] was updated.", updateddIdentity.getUsername(), updateddIdentity.getId());
		// result
		return new DefaultEventResult<>(event, this);
	}
 
	@Override
	public int getOrder() {
		// right after identity update
		return CoreEvent.DEFAULT_ORDER + 1;
	}
}

Asynchronous processor

If we want to implement the same feature as above but asynchronously, we can process asynchronous IdentityEventType.NOTIFY instead IdentityEventType.UPDATE. When we need to change synchronous processors to asynchronous, we can simply change processed event type and add some condition, when only some original event types has to be processed ⇒ asynchronous NOTIFY event type is published for CREATE, UPDATE and EAV_SAVE event types.

@Enabled(ExampleModuleDescriptor.MODULE_ID)
@Component("exampleLogIdentityUpdateAsyncProcessor")
@Description("Logs after identity is updated")
public class LogIdentityUpdateAsyncProcessor
		extends CoreEventProcessor<IdmIdentityDto> 
		implements IdentityProcessor {
 
	/**
	 * Processor's identifier - has to be unique by module
	 */
	public static final String PROCESSOR_NAME = "log-identity-update-async-processor";
	private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory
			.getLogger(LogIdentityUpdateAsyncProcessor.class);
 
	public LogIdentityUpdateAsyncProcessor() {
		// processing identity NOTIFY event only
		super(IdentityEventType.NOTIFY);
	}
 
	@Override
	public boolean conditional(EntityEvent<IdmIdentityDto> event) {
		// we want to process original UPDATE event only
		// async NOTIFY event is published for CREATE, UPDATE, EAV_SAVE event types
		return super.conditional(event)
				&& IdentityEventType.UPDATE.name().equals(event.getProperties().get(EntityEventManager.EVENT_PROPERTY_PARENT_EVENT_TYPE));
	}
 
	@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 updateddIdentity = event.getContent();
		// log
		LOG.info("Identity [{},{}] was updated.", updateddIdentity.getUsername(), updateddIdentity.getId());
		// result
		return new DefaultEventResult<>(event, this);
	}
 
	@Override
	public int getOrder() {
		// notify event has their own process line - we can use default order
		return CoreEvent.DEFAULT_ORDER;
	}
}

Future development