Events

Introduction

When the framework renders a domain object, and as the end-user interacts with the domain object, the framework it emits multiple events using the intra-process event bus. These events enable other domain services (possibly in other modules) to influence how the domain object is rendered, or to perform side-effects or even veto an action invocation.

The framework has several categories of events: domain events, UI events and lifecycle events. Learn more about these in events page.

To receive the events, the domain service should subscribe to the EventBusService, and implement an appropriately annotated method to receive the events.

Domain Events

Domain events are fired for every user interaction with each object member (property, collection or action).

By default, rendering a property causes a PropertyDomainEvent to be fired, though the @Property#domainEvent() attribute allows a custom subclass to be specified if necessary. Similarly, rendering a collection causes a DomainEvent to be fired, and rendering an action causes an ActionDomainEvent to be fired.

In fact, each event can be fired up to five times, with the event’s getEventPhase() method indicating to the subscriber the phase:

  • hide phase allows the subscriber to hide the member

  • disable phase allows the subscriber to disable the member.

    For a property this makes it read-only; for an action this makes it "greyed out". (Collections are implicitly read-only).

  • validate phase allows the subscriber to validate the proposed change.

    For a property this means validating the proposed new value of the property; for an action this means validating the action parameter arguments. For example, a referential integrity restrict could be implemented here.

  • executing phase is prior to the actual property edit/action invocation, allowing the subscriber to perform side-effects.

    For example, a cascade delete could be implemented here.

  • executed phase is after the actual property edit/action invocation.

    For example, a business audit event could be implemented here.

For more details on the actual domain event classes, see the domain event section of the relevant reference guide.

UI Events

To allow the end-user to distinguish one domain object from another, it is rendered with a title and an icon.

The code to return title and icon of an object is typically part of the domain object’s implementation, with the title() method providing the title string, and (occasionally) iconName() allowing the icon file (usually a .png) to be specified. (For more on this, see the page earlier).

However, UI events allow this title and icon to be provided instead by a subscriber. UI events have higher precedence than the other mechanisms of supplying a title.

If annotated with @DomainObjectLayout#titleUiEvent(), the appropriate (subclass of) TitleUiEvent will be emitted. A subscriber can then provide the title on behalf of the domain object. Similarly for #iconUiEvent().

In addition, it is possible to use events to obtain a CSS class to render with the domain object, using #cssClassUiEvent(), and to select an alternate layout file using #layoutUiEvent().

There are two use cases where this feature is useful:

  • the first is to override the title/icon/CSS class/layout of library code, for example as provided by the SecMan extension.

  • the second is for JAXB-style view models which are code generated from XSDs and so cannot have any dependencies on the rest of the Apache Causeway framework.

In this second case a subscriber on the default events can provide a title and icon for such an object, with the behaviour provided using mixins.

Lifecycle Events

Lifecycle events allow domain object subscribers to listen for changes to the persistence state of domain entities, and act accordingly.

Lifecycle events are not fired for view models.

The lifecycle events supported are:

  • created

    Entity has just been instantiated. Note that this requires that the object is instantiated using the framework, see here for further discussion.

  • loaded

    Entity has just retrieved/rehydrated from the database

  • persisting

    Entity is about to be inserted/saved (ie for the first time) into the database

  • persisted

    Entity has just been inserted/saved (ie for the first time) into the database

  • updating

    The (already persistent) entity about to be flushed in the database

  • updated

    The (already persistent) entity has just been flushed to the database

  • removing

    The (already persistent) entity is about to be deleted from the database

For example, if annotated with @DomainObjectLayout#updatingLifecycleEvent, the appropriate (subclass of) ObjectUpdatingEvent will be emitted.

There is no lifecycle event for "entity creating" because (obviously) the framework doesn’t know about newly created objects until they have been created. Similarly, there is no lifecycle event for entities that have been removed because it is not valid to "touch" a domain entity once deleted.

Custom Events

The built-in events provided by the framework (domain, UI, lifecycle) are each emitted at well-defined points when the domain object is interacted with through the framework. Sometimes though there is a need to emit events programmatically. This can be done using the EventBusService.

Define the Event Class

First define the event class.

  • It’s common to define a base class in fact, for example:

    StateTransitionEvent.java
    public abstract class StateTransitionEvent<
            DO,
            S extends State<S>
            > extends java.util.EventObject {
    
        private final DO domainObject;
    
        public StateTransitionEvent(
                final DO domainObject) {
            super(domainObject);
            this.domainObject = domainObject;
        }
    
        @Override
        public DO getSource() {
            return (DO) super.getSource();
        }
    }

    As the example shows, you might wish to inherit from java.util.EventObject, but there’s no strict requirement to do so.

  • if we have a base class, then we need a concrete subclass:

    InvoiceApprovalTransitionEvent.java
    public static class InvoiceApprovalTransitionEvent
            extends StateTransitionEvent<
            IncomingInvoice,
            IncomingInvoiceApprovalState> {
        public InvoiceApprovalTransitionEvent( final IncomingInvoice domainObject) {
            super(domainObject, stateTransitionIfAny, transitionType);
        }
    }

Post the event

With the event class fully defined, we simply instantiate and post:

val event = new InvoiceApprovalTransitionEvent(
                invoice, IncomingInvoiceApprovalState.APPROVED);

eventBusService.post(event);

Subscribe to the event

The subscribers of custom events are defined much the same way as subscribers of the framework’s built-in events. For example:

IncomingInvoiceSubscriber.java
@Service
public class IncomingInvoiceSubscriber {

    @EventListener(InvoiceApprovalTransitionEvent.class)
    public void on(InvoiceApprovalTransitionEvent ev) {
        // ...
    }
}

Event Subscribers

Domain services acting as event subscribers can subscribe to lifecycle events, influencing the rendering and behaviour of other objects.

Behind the scenes this uses Spring’s (in-memory) event bus, and so the way to subscribe is using Spring’s @EventListener annotation.

import org.springframework.context.event.EventListener;
// ...

@Service                                                        (1)
@RequiredArgsConstructor(onConstructor_ = {@Inject} )
public class OnCustomerDeletedCascadeDeleteOrders {

    private final OrderRepository orderRepository;

    @EventListener(Customer.DeletedEvent.class)                 (2)
    public void on(final Customer.DeletedEvent ev) {            (3)
        Customer customer = ev.getSource();
        orderRepository.delete(customer);
    }
}
1 use Spring Framework’s @EventListener
2 the parameter type of the method corresponds to the event emitted on the event bus. The actual method name does not matter (though it must have public visibility).