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
) 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
- event is created with given content <code java> EntityEvent<IdmIdentityDto> event = new IdentityEvent(IdentityEventType.UPDATE, updateIdentity); </code>
- then is published via
EntityEventManager
<code java> EventContext<IdmIdentityDto> context = entityEventManager.process(event); </code> - when event is published and their content is descendant of
AbstractDto
, then original source is filled to the event - original source contains previously persisted (original) dto and could be used in "check modification" processors. If event creating new dto, then original source isnull
. Original source could be set externally - then no automatic filling occurs. - returning context contains results from all reacting processors in defined processors order.
- the processor can label the event as (
closed
) or (suspended
) and therefore skip all the other processors. If the suspended event is published again viaEntityEventManager
, the processing will continue where it was suspended, if context (with processed results) is preserved. If the processing of the event is (suspended
), the called method should return the adequateaccepted
state. - when event walk through procesors, then event's processed order is incremented - this order is used after event suspending and run again - event proccessing will continue with processor with next order.
Supported events
Supported events for individual entities:
IdmIdentityDto
- operation with the identityIdmRoleDto
- operation with the roleIdmRoleCatalogueDto
- operation with the role catalogueIdmIdentityRoleDto
- assigning a role to the userIdmIdentityContractDto
- labor-law relationIdmRoleTreeNodeDto
- automatic roleIdmContractGuaranteeDto
- manually added guarantee to contractIdmTreeNodeDto
- tree structure nodeIdmPasswordPolicyDto
- password policyIdmLongRunningTaskDto
- long running taskAccAccountDto
- Accounts on target systemSysSystemDto
- System in ACC moduleSysSystemMappingDto
- Mapping between system in ACC and his mapping of provisioning or syncSysSchemaAttributeDto
- Connector schema on system in ACC moduleSysProvisioningOperationDto
- provisioning operation (ACC)SysSyncConfigDto
,SysSyncItemLogDto
- synchronization in ACC moduleVsRequestDto
- Request for account change in virtual system (VS module)RptReportDto
- generate report
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):
core
: 0provisioning
: 1000
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 } ...
Basic interfaces
EntityEvent
- an event migrating through the processors. The content of the event can beBaseEntity
,BaseDto
or any serializable content.EventContext
- holds the context of the processed event - which processors it has been processed by, with what results, if the processing is suspended, closed, etc.EventResult
- the result of processing of the event by one processor.EntityEventProcessor
- event processor. Processor has to have unique identifier by module.EntityEventManager
- ensures publishing of the event to processors.EventableDtoService
- adds event processing support to service
Basic classes
AbstractEntityEvent
- an abstract event migrating through the processors - when adding a proper one can be simply inherited from.DefaultEventContext
- the default context of the processed event - all abstract and default events and processors use it.DefaultEventResult
- The default event result processed by one processor - all abstract and default events and processors use it.AbstractEntityEventProcessor
- abstract event processor - when adding a proper one can be simply inherited from.AbstractApprovableEventProcessor
- the event processor will send the whole event with dto (or serializable) content to WF for approval. It is necessary to configure the definition of the WF where the event will be sent to.DefaultEntityEventManager
- ensures publishing of the events to processors.AbstractEventableDtoService
- adds default event processing support to service.
AbstractEntityEventProcessor
Use this super class, when creating new processor implementation (this class contains some boring parts).
Methods, which have to be implemented:
getName()
- Unique (module scope) configurable object identifier. Its used in configuration key etc.process(event)
- the main processors method with business logic.getOrder()
- when will be processor processed. Processors are executed in defined order.
Methods, which could be implemented [optional]:
supports(event)
- Returnstrue
, when processor supports given event. Default implementation takes processor's template entity class and event type given in constructor (or configured byeventTypes
property).conditional(event)
- Returnstrue
, when processor supports given event. Returnstrue
by default. Override this method for adding some condition.isClosable()
- Returnstrue
, when processor could close event (only documentation purpose now). Returnsfalse
by default.
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:
- simple adding of validation or referential integrity
- repeating the whole chain
and disadvantages as well:
- having to catch all the exceptions properly to avoid "breaking the chain"
- saving logs and archives in the new transaction (
Propagation.REQUIRES_NEW
)
Predefined processors order
- 0 - basic / core functionality - operation
save
,delete
etc. - 100 - automatic roles computation.
- 1000 - after
save
- e.g. sends notifications. - -1000 - before
delete
provisioning (before identity role is deleted). - Identity:
- -2000 - validate password in acc module - checks all system password policies and idm default policy ⇒ all policies are evaluated in one request. If acc module is enabled, then core password validation processor can be disabled.
- -1000 - validate password in core module - checks idm default policy.
- 100 - persist password
- Contract:
- 100 - Automatic roles recount while identity contract is saved, updated or deleted / disabled.
- 200 - Contract exclusion, end and enable.
- LongRunningTask:
- 100 - execute scheduled long running tasks, which depends on currently ended scheduled task.
- Provisioning:
- -5000 - check disabled system
- -1000 - compute attributes for provisioning (read attribute values from target system)
- -500 - check readonly system
- 0 - execute provisioning (create / update / delete)
- 1000 - execute
after
provisioning actions (e.g. sends notifications) - 5000 - archive processed provisioning operation.
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
- Check if defined fields on identity was changed. If yes, then send notification.
- As default is used this system template identityMonitoredFieldsChanged.
- Extended attributes is not supported now.
- Order of processor is Integer.Max - 100. We want to send notification on end of chain (after identity is presisted or provisioning are completed).
# 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
- Name of processor "role-request-approval-processor".
- This process ensures the approval of the request for change premissions.
# 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 approving task will be assigned to all users with role Helpdesk.
# 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
- The approving task will be assigned to all users evaluated as the managers of the applicant. The manager is defined based on the industrial relations of the applicant.
# Default is disabled idm.sec.core.wf.approval.manager.enabled=false
Change user permissions workflow - Approval by the user administration department
- The approving task will be assigned to all users with role Usermanager.
# 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 after manually add, update or remove guarantee is controlled by these two processors: ContractGuaranteeSaveProvisioningProcessor and ContractGuaranteeDeleteProvisioningProcessor. Provisioning for update or create is done after success save entity, but provisioning for delete is done before delete entity. Both processors are enabled by default.
## 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
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 AbstractIdentityProcessor { /** * 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; } }