@DomainObject

Domain semantics for domain objects (entities and view models; for services see DomainService ).

If - for the currently logged on user - none of the domain object’s members are effectively visible, (or if there are no members to begin with), the object instance is considered hidden. Hence a NOT-AUTHORIZED page will be displayed instead.

API

DomainObject.java
@interface DomainObject {
  String[] aliased() default {};     (1)
  Class<?> autoCompleteRepository() default Object.class;     (2)
  String autoCompleteMethod() default "autoComplete";     (3)
  Bounding bounding() default Bounding.NOT_SPECIFIED;     (4)
  Editing editing() default Editing.NOT_SPECIFIED;     (5)
  String editingDisabledReason() default "";     (6)
  Publishing entityChangePublishing() default Publishing.NOT_SPECIFIED;     (7)
  Introspection introspection() default Introspection.NOT_SPECIFIED;     (8)
  String mixinMethod() default "$$";     (9)
  Nature nature() default Nature.NOT_SPECIFIED;     (10)
  Class<? extends ObjectCreatedEvent<?>> createdLifecycleEvent() default ObjectCreatedEvent.Default.class;     (11)
  Class<? extends ObjectPersistingEvent<?>> persistingLifecycleEvent() default ObjectPersistingEvent.Default.class;     (12)
  Class<? extends ObjectPersistedEvent<?>> persistedLifecycleEvent() default ObjectPersistedEvent.Default.class;     (13)
  Class<? extends ObjectLoadedEvent<?>> loadedLifecycleEvent() default ObjectLoadedEvent.Default.class;     (14)
  Class<? extends ObjectUpdatingEvent<?>> updatingLifecycleEvent() default ObjectUpdatingEvent.Default.class;     (15)
  Class<? extends ObjectUpdatedEvent<?>> updatedLifecycleEvent() default ObjectUpdatedEvent.Default.class;     (16)
  Class<? extends ObjectRemovingEvent<?>> removingLifecycleEvent() default ObjectRemovingEvent.Default.class;     (17)
  Class<? extends ActionDomainEvent<?>> actionDomainEvent() default ActionDomainEvent.Default.class;     (18)
  Class<? extends PropertyDomainEvent<?, ?>> propertyDomainEvent() default PropertyDomainEvent.Default.class;     (19)
  Class<? extends CollectionDomainEvent<?, ?>> collectionDomainEvent() default CollectionDomainEvent.Default.class;     (20)
  int queryDslAutoCompleteMinLength() default QueryDslAutoCompleteConstants.MIN_LENGTH;     (21)
  int queryDslAutoCompleteLimitResults() default QueryDslAutoCompleteConstants.LIMIT_RESULTS;     (22)
  Class<?> queryDslAutoCompleteAdditionalPredicateRepository() default Object.class;     (23)
  String queryDslAutoCompleteAdditionalPredicateMethod() default QueryDslAutoCompleteConstants.ADDITIONAL_PREDICATE_METHOD_NAME;     (24)
}
1 aliased

Alternative logical type name(s) for the annotated type.

2 autoCompleteRepository

The class of the domain service that provides an autoComplete(String) method.

3 autoCompleteMethod

The method to use in order to perform the auto-complete search (defaults to "autoComplete").

4 bounding

Indicates that the class has a bounded, or finite, set of instances.

5 editing

Whether the properties of this domain object can be edited, or collections of this object be added to/removed from.

6 editingDisabledReason

If #editing() is set to Editing#DISABLED , then the reason to provide to the user as to why the object’s properties cannot be edited/collections modified.

7 entityChangePublishing

Whether entity changes (persistent property updates) should be published to org.apache.causeway.applib.services.publishing.spi.EntityPropertyChangeSubscriber s and whether entity changes, captured as org.apache.causeway.applib.services.publishing.spi.EntityChanges , should be published to org.apache.causeway.applib.services.publishing.spi.EntityChangesSubscriber s.

8 introspection

Controls on a per class basis, how meta-model class introspection should process members, supporting methods and callback methods.

9 mixinMethod

Applicable only if #nature() is Nature#MIXIN , indicates the name of the method within the mixin class to be inferred as the action of that mixin.

10 nature

The nature of this domain object.

11 createdLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectCreatedEvent .

12 persistingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectPersistingEvent .

13 persistedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectPersistedEvent .

14 loadedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectLoadedEvent .

15 updatingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectUpdatingEvent .

16 updatedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectUpdatedEvent .

17 removingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectRemovingEvent .

18 actionDomainEvent

Indicates that an invocation of any action of the domain object (that do not themselves specify their own @Action(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using the specified custom (subclass of) ActionDomainEvent .

19 propertyDomainEvent

Indicates that changes to any property of the domain object (that do not themselves specify their own @Property(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using the specified custom (subclass of) PropertyDomainEvent .

20 collectionDomainEvent

Indicates that changes to any collection of the domain object (that do not themselves specify their own @Collection(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) CollectionDomainEvent .

21 queryDslAutoCompleteMinLength

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute determines the minimum number of characters that must be entered before the query is submitted. Fine-tunes how auto-complete queries work, Whether to use the value of this (string) property for auto.

22 queryDslAutoCompleteLimitResults

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to limit the number of rows that are returned.

23 queryDslAutoCompleteAdditionalPredicateRepository

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to specify additional predicate(s) to always be added to the autocomplete (for example to search only for current or active objects).

24 queryDslAutoCompleteAdditionalPredicateMethod

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to specify the name of a method in a repository to provide additional predicate(s) to always be added to the autocomplete (for example to search only for current or active objects).

Members

aliased

Alternative logical type name(s) for the annotated type.

autoCompleteRepository

The class of the domain service that provides an autoComplete(String) method.

It is sufficient to specify an interface rather than a concrete type.

autoCompleteMethod

The method to use in order to perform the auto-complete search (defaults to "autoComplete").

The method is required to accept a single string parameter, and must return a list of the domain type.

bounding

Indicates that the class has a bounded, or finite, set of instances.

Takes precedence over auto-complete.

Note: this replaces bounded=true|false prior to v2.x

editing

Whether the properties of this domain object can be edited, or collections of this object be added to/removed from.

Note that non-editable objects can nevertheless have actions invoked upon them.

editingDisabledReason

If #editing() is set to Editing#DISABLED , then the reason to provide to the user as to why the object’s properties cannot be edited/collections modified.

If left empty (default), no reason is given.

entityChangePublishing

Whether entity changes (persistent property updates) should be published to org.apache.causeway.applib.services.publishing.spi.EntityPropertyChangeSubscriber s and whether entity changes, captured as org.apache.causeway.applib.services.publishing.spi.EntityChanges , should be published to org.apache.causeway.applib.services.publishing.spi.EntityChangesSubscriber s.

introspection

Controls on a per class basis, how meta-model class introspection should process members, supporting methods and callback methods.

mixinMethod

Applicable only if #nature() is Nature#MIXIN , indicates the name of the method within the mixin class to be inferred as the action of that mixin.

Supporting methods are then derived from that method name. For example, if the mixin method name is "act", then the disable supporting method will be "disableAct".

Typical examples are "act", "prop", "coll", "exec", "execute", "invoke", "apply" and so on. The default name is $$.

*NOTE* : it's more typical to instead use xref:refguide:applib:index/annotation/Action.adoc[Action] , xref:refguide:applib:index/annotation/Property.adoc[Property] or xref:refguide:applib:index/annotation/Collection.adoc[Collection] as the class-level annotation, indicating that the domain object is a mixin. The mixin method name for these is, respectively, "act", "prop" and "coll".

nature

The nature of this domain object.

Most common are natures of Nature#ENTITY and Nature#VIEW_MODEL . For mixins, rather than use a nature of Nature#MIXIN , it’s more typical to instead use Action , Property or Collection as the class-level annotation, indicating that the domain object is a mixin. The #mixinMethod() mixin method name for these is, respectively, "act", "prop" and "coll".

The Nature#BEAN nature is for internally use, and should not normally be specified explicitly.

createdLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectCreatedEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

persistingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectPersistingEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

persistedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectPersistedEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

loadedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectLoadedEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

updatingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectUpdatingEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

updatedLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectUpdatedEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

removingLifecycleEvent

Indicates that the loading of the domain object should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) ObjectRemovingEvent .

This subclass must provide a no-arg constructor; the fields are set reflectively.

actionDomainEvent

Indicates that an invocation of any action of the domain object (that do not themselves specify their own @Action(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using the specified custom (subclass of) ActionDomainEvent .

For example:

@DomainObject(actionDomainEvent=SomeObject.GenericActionDomainEvent.class)
public class SomeObject{
    public static class GenericActionDomainEvent extends ActionDomainEvent<Object> { ... }

    public void changeStartDate(final Date startDate) { ...}
    ...
}

This will result in all actions as a more specific type to use) to emit this event.

This subclass must provide a no-arg constructor; the fields are set reflectively. It must also use Object as its generic type. This is to allow mixins to also emit the same event.

propertyDomainEvent

Indicates that changes to any property of the domain object (that do not themselves specify their own @Property(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using the specified custom (subclass of) PropertyDomainEvent .

For example:

@DomainObject(propertyDomainEvent=SomeObject.GenericPropertyDomainEvent.class)
public class SomeObject{

   public LocalDate getStartDate() { ...}
}

This subclass must provide a no-arg constructor; the fields are set reflectively. It must also use Object as its generic type. This is to allow mixins to also emit the same event.

collectionDomainEvent

Indicates that changes to any collection of the domain object (that do not themselves specify their own @Collection(domainEvent=…​) should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService event bus using a custom (subclass of) CollectionDomainEvent .

For example:

@DomainObject(collectionDomainEvent=Order.GenericCollectionDomainEvent.class)
public class Order {

  public SortedSet<OrderLine> getLineItems() { ...}
}

This subclass must provide a no-arg constructor; the fields are set reflectively. It must also use Object as its generic type. This is to allow mixins to also emit the same event.

queryDslAutoCompleteMinLength

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute determines the minimum number of characters that must be entered before the query is submitted. Fine-tunes how auto-complete queries work, Whether to use the value of this (string) property for auto.

this feature requires that the querydsl-xxx module (for JDO or JPA as required) is included as part of the application manifest. Otherwise, no autocomplete will be generated.
if DomainObject#autoCompleteRepository() (and DomainObject#autoCompleteMethod() ) have been specified, then these take precedence of the query DSL auto-complete.

queryDslAutoCompleteLimitResults

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to limit the number of rows that are returned.

if DomainObject#autoCompleteRepository() (and DomainObject#autoCompleteMethod() ) have been specified, then these take precedence of the query DSL auto-complete.
this feature requires that the querydsl-xxx module (for JDO or JPA as required) is included as part of the application manifest. Otherwise, no autocomplete will be generated.

queryDslAutoCompleteAdditionalPredicateRepository

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to specify additional predicate(s) to always be added to the autocomplete (for example to search only for current or active objects).

If this attribute is specified, it indicates the class of a repository service that includes a method which returns an additional predicate to be applied. The default name of that method is "queryDslAutoCompleteAdditionalPredicates" (but can be overridden if required using DomainObject#queryDslAutoCompleteAdditionalPredicateMethod() ).

this feature requires that the querydsl-xxx module (for JDO or JPA as required) is included as part of the application manifest. Otherwise, no autocomplete will be generated.

queryDslAutoCompleteAdditionalPredicateMethod

If at least one property of the entity has been annotated with Property#queryDslAutoComplete() , then that property (and any others) will automatically be used for autocomplete functionality; this attribute can be used to specify the name of a method in a repository to provide additional predicate(s) to always be added to the autocomplete (for example to search only for current or active objects).

this feature requires that the querydsl-xxx module (for JDO or JPA as required) is included as part of the application manifest. Otherwise, no autocomplete will be generated.

Example

For example:

@DomainObject(
    auditing=Auditing.ENABLED,
    autoCompleteRepository=CustomerRepository.class
    editing=Editing.ENABLED,
    updatedLifecycleEvent=Customer.UpdatedEvent.class

)
public class Customer {
    ...
}

View Models

The @DomainObject(nature=VIEW_MODEL) annotation, applied to a class, indicates that the class is a view model.

View models are not persisted to the database, instead their state is encoded within their identity (ultimately represented in the URL).

For example:

@DomainObject(nature=VIEW_MODEL)
public class CustomerViewModel {
    public CustomerViewModel() {}
    public CustomerViewModel(String firstName, int lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    ...
}

Although there are several ways to instantiate a view model, we recommend that they are instantiated using an N-arg constructor that initializes all relevant state. The ServiceRegistry can then be used to inject dependencies into the view model. For example:

Customer cust = ...
CustomerViewModel vm = factoryService.viewModel(
    new CustomerViewModel(cust.getFirstName(), cust.getLastName()));

The view model’s memento will be derived from the value of the view model object’s properties. Any properties annotated with @Programmatic will be excluded from the memento. Properties that are merely hidden are included in the memento.

View models when defined using @DomainObject(nature=VIEW_MODEL) have some limitations:

  • view models cannot hold collections other view models (simple properties are supported, though)

  • collections (of either view models or entities) are ignored.

  • not every data type is supported,

However, these limitations do not apply to JAXB view models. If you are using view models heavily, you may wish to restrict yourself to just the JAXB flavour.

Object aliases

The logicalTypeName() element is used to provide a unique alias for the object’s class name.

This value is used internally to generate a string representation of an objects identity (the Oid). This can appear in several contexts, including:

Examples

For example:

@Named("orders.Order")
@DomainObject
public class Order {
    ...
}

Precedence

The rules of precedence are:

  1. @DomainObject#logicalTypeName

  2. ORM-specific:

    1. JDO: @PersistenceCapable, if at least the schema attribute is defined.

      If both schema and table are defined, then the value is “schema.table”. If only schema is defined, then the value is “schema.className”.

    2. (JPA) @Table#schema().

  3. Fully qualified class name of the entity.

This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain objects.

Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the object will break. At best this will require a data migration in the database; at worst it could cause external clients, eg if accessing data through the Restful Objects viewer, to break.

If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot. An error message will be printed in the log to help you determine which classes have duplicate object tyoes.

Nature

The nature() element is used to characterize the domain object as either an entity (part of the domain layer) or as a view model (part of the application layer). If the domain object should be thought of as an entity, it also captures how the persistence of that entity is managed.

For example:

@DomainObject(nature=Nature.VIEW_MODEL)
public class PieChartAnalysis {
    ...
}

Specifically, the nature must be one of:

  • NOT_SPECIFIED,

    (the default); specifies no paricular semantics for the domain class.

  • ENTITY

    indicates that the domain object is an entity whose persistence is managed internally by Apache Causeway, using the JDO/DataNucleus objectstore.

  • MIXIN

    indicates that the domain object is part of the domain layer, and is contributing behaviour to objects of some other type as a mixin (also known as a trait).

    For further discussion on using mixins, see mixins in the user guide.

  • VIEW_MODEL

    indicates that the domain object is conceptually part of the application layer, and exists to surfaces behaviour and/or state that is aggregate of one or more domain entities.

    • Variant: External Entity

      Or indicates that the domain object is a wrapper/proxy/stub (choose your term) to an entity that is managed by some related external system. For example, the domain object may hold just the URI to a RESTful resource of some third party REST service, or the id of some system accessible over SOAP.

      The identity of an external entity is determined solely by the state of entity’s properties. The framework will automatically recreate the domain object each time it is interacted with.

    • Variant: In-Memory Entity

      Or indicates that the domain object is a wrapper/proxy/stub to a "synthetic" entity, for example one that is constructed from some sort of internal memory data structure.

      The identity of an in-memory entity is determined solely by the state of entity’s properties. The framework will automatically recreate the domain object each time it is interacted with.

Those natures that indicate the domain object is an entity (of some sort or another) mean then that the domain object is considered to be part of the domain model layer.

For VIEW_MODEL domain objects the state of the object is encoded into its internal OID (represented ultimately as its URL), and is recreated directly from that URL.

Because this particular implementation was originally added to Apache Causeway in support of view models, the term was also used for the logically different external entities and in-memory entities.

The benefit of nature() is that it allows the developer to properly characterize the layer (domain vs application) that an entity lives, thus avoiding confusion as "view model" (the implementation technique) and "view model" (the application layer concept).

On the other hand, view models defined in this way do have some limitations; see View Models for further discussion.

These limitations do not apply to JAXB view models. If you are using view models heavily, you may wish to restrict yourself to just the JAXB flavour.

Editing

The editing() element determines whether a domain object’s properties and collections are not editable (are read-only).

The default is AS_CONFIGURED, meaning that the causeway.applib.annotation.domain-object.editing configuration property is used to determine the whether the object is modifiable:

  • true

    the object’s properties and collections are modifiable.

  • false

    the object’s properties and collections are read-only, ie not modifiable.

If there is no configuration property in application.properties then object are assumed to be modifiable.

In other words, editing can be disabled globally by setting the causeway.applib.adoc#causeway.applib.annotation.domain-object.editing configuration property:

causeway.applib.annotation.domain-object.editing=false

We recommend this setting; it will help drive out the underlying business operations (processes and procedures) that require objects to change; these can then be captured as business actions.

This default can be overridden on an object-by-object basis; if editing() is set to ENABLED then the object’s properties and collections are editable irrespective of the configured value; if set to DISABLED then the object’s properties and collections are not editable irrespective of the configured value.

For example:

@DomainObject(
    editing=Editing.DISABLED,
    editingDisabledReason="Reference data, so cannot be modified"
)
public class Country {
    ...
}

Domain Event Defaults

Whenever an member of a domain object is interacted with then a domain event will be fired, for each of the various phases (hidden, disabled, validated, executing, executed).

For each of the three types of members (action, property and collection), the DomainObject provides an element that defines a common event domain class to be fired whenever interacting with members of that type. This default event type can be overridden with a more specific event type if necessary.

Type

@DomainObject default

Overridden with

Actions

Properties

Collections

Action example

For an action:

@DomainObject(
    actionDomainEvent=ToDoItem.ActionDomainEventDefault.class
)
public class ToDoItem {

    public static class ActionDomainEventDefault extends
        org.apache.causeway.applib.events.domain.ActionDomainEvent<Object> { }
    // ...

    public void updateDescription(final String description) {
        this.description = description;
    }

}

The specified class will also be used as the event for any mixin action contributed to the class.

Note though one small difference between the events emitted by a "regular" action and a contributed action is that the source of the event (as in event#getSource() will be different. In the former case it will be the domain object instance, in the latter it will be the mixin object instantiated automatically by the framework.

However, the domain object is available using event#getMixedIn(). Even easier, event#getSubject() will always return the domain object (it returns the #getMixedIn() if present, otherwise the #getSource().

Action domain events have generic types, with the (first and only) generic type indicating the event’s source’s type.

Because an event specified at the class level might have either the domain object or a mixin for the domain object as its source, they should therefore use simply Object as their generic type.

Property example

For example:

@DomainObject(
    propertyDomainEvent=ToDoItem.PropertyDomainEventDefault.class
)
public class ToDoItem {
    public static class PropertyDomainEventDefault
        extends org.apache.causeway.applib.events.domain.PropertyDomainEvent<Object> { }
    ...

    @Getter @Setter
    private String description;
}

If there is a mixin for the domain object, then this will also honour the domain event. For example:

@Property(
	propertyDomainEvent=ToDoItem_priority.PropertyDomainEventDefault.class
)
public class ToDoItem_priority {
    private final ToDoItem todoItem;
    // constructor omitted

    public static class PropertyDomainEventDefault
        extends org.apache.causeway.applib.events.domain.PropertyDomainEvent<Object> { }

    public Integer prop() { /* ... */ }
}

Interactions with the property contributed by this mixin will emit the domain event of the subject (ToDoItem).

One small difference between the events emitted by a "regular" property and a contributed property is that the source of the event (as in event#getSource() will be different. In the former case it will be the domain object instance, in the latter it will be the mixin object instantiated automatically by the framework.

However, the domain object is available using event#getMixedIn(). Even easier, event#getSubject() will always return the domain object (it returns the #getMixedIn() if present, otherwise the #getSource().

Property domain events have generic types, with the first generic type indicating the event’s source’s type, and the second generic type indicating the property return type.

Because an event specified at the class level might have either the domain object or a mixin for the domain object as its source, they should therefore use simply Object as their first generic type.

They should also have Object for their second generic type, because the return type of the various properties of the domain object will or could be different.

Collection example

For example:

import lombok.Getter;
import lombok.Setter;

@DomainObject(
    collectionDomainEvent=ToDoItem.CollectionDomainEventDefault.class
)
public class ToDoItem {
    public static class CollectionDomainEventDefault extends
            org.apache.causeway.applib.events.domain.CollectionDomainEvent<Object> { }
    ...

    @Getter @Setter
    private Set<Category> categories = new TreeSet<>();
}

If there is a mixin for the domain object, then this will also honour the domain event. For example:

public class ToDoItem {

    @Collection
    @CollectionLayout(defaultView = "table")
    public class related {

        public List<ToDoItem> coll() {
            // ...
        }
    }

}

Interactions with the collection contributed by this mixin will emit the domain event of the subject (ToDoItem).

One small difference between the events emitted by a "regular" collection and a contributed action is that the source of the event (as in event#getSource() will be different. In the former case it will be the domain object instance, in the latter it will be the mixin object instantiated automatically by the framework.

However, the domain object is available using event#getMixedIn(). Even easier, event#getSubject() will always return the domain object (it returns the #getMixedIn() if present, otherwise the #getSource().

Collection domain events have generic types, with the first generic type indicating the event’s source’s type, and the second generic type indicating the element type.

Because an event specified at the class level might have either the domain object or a mixin for the domain object as its source, they should therefore use simply Object as their first generic type.

They should also have Object for their second generic type, because the element type of the various collections of the domain object will or could be different.

Lifecycle Events

Whenever the framework interacts with a domain entity, lifecycle events are fired. These are:

  • instantiated

    through FactoryService. An event is also fired for view models

  • persisting

    an entity is about to be persisted (INSERTed) into the database

  • persisted

    an entity has just been persisted to the database

  • loaded

    an already persistent entity has just been loaded from the database

  • updating

    an already persistent entity is about to be saved (UPDATEd) to the database

  • updated

    an already persistent entity has just been saved (UPDATEd) to the database

  • removing

    an already persistent entity is about to be removed (DELETEd) from the database

The framework has a built-in event class (for each lifecycle hint) that is raised by default; for example a ObjectUpdatingEvent.Default is raised when an object is about to be updated. Subscribers subscribe through the EventBusService and can use the event to obtain a reference to the object just created.

This basic model can be influenced in a couple of ways:

  • first, it is also possible to suppress any events from being emitted using a configuration property (a different config property exists each lifecycle hook).

  • second, an element on DomainObject annotation (for each lifecycle hook) allows a different event subtype to be emitted instead

    This allows subscribers to more targeted as to the events that they subscribe to.

The framework also provides convenience "Doop" and a "Noop" event classes, that provoke these behaviours:

  • if the appropriate DomainObject' element is set to the "Doop" subclass, then this will event will be fired

    this is a convenient way of ensuring an event is fired even if the hook has been disabled globally, but without the hassle of defining a custom event type

  • if set to the "Noop" subclass, then an event will not be fired.

The table below summarises all the lifecycle hooks:

Table 1. Lifecycle events

Lifecycle event

Config property

@DomainObject override

Framework event types

created

ObjectCreatedEvent.Default
ObjectCreatedEvent.Doop
ObjectCreatedEvent.Noop

loaded

ObjectLoadedEvent.Default
ObjectLoadedEvent.Doop
ObjectLoadedEvent.Noop

persisting

ObjectPersistingEvent.Default
ObjectPersistingEvent.Doop
ObjectPersistingEvent.Noop

persisted

ObjectPersistedEvent.Default
ObjectPersistedEvent.Doop
ObjectPersistedEvent.Noop

updating

ObjectUpdatingEvent.Default
ObjectUpdatingEvent.Doop
ObjectUpdatingEvent.Noop

updated

ObjectUpdatedEvent.Default
ObjectUpdatedEvent.Doop
ObjectUpdatedEvent.Noop

removing

ObjectRemovingEvent.Default
ObjectRemovingEvent.Doop
ObjectRemovingEvent.Noop

Subscribers

Subscribers can be either coarse-grained (if they subscribe to the top-level event type):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {
    @EventListener(ObjectCreatedEvent.class)
    public void on(ObjectCreatedEvent ev) {
        if(ev.getSource() instanceof ToDoItem) {
            ...
        }
    }
}

or can be fine-grained (by subscribing to specific event subtypes):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {
    @EventListener(ToDoItem.ObjectCreatedEvent.class)
    public void on(ToDoItem.ObjectCreatedEvent ev) {
        ...
    }
}

Examples

created example

For example:

@DomainObjectLayout(
    createdLifecycleEvent=ToDoItem.CreatedEvent.class
)
public class ToDoItem {
    public static class CreatedEvent
        extends org.apache.causeway.applib.events.lifecycle.ObjectCreatedEvent<ToDoItem> { }
    ...
}

It’s possible to instantiate objects without firing this lifecycle; just instantiate using its regular constructor, and then use the ServiceInjector's injectServicesInto(…​) to manually inject any required domain services.

persisting example

For example:

@DomainObjectLayout(
    persistingLifecycleEvent=ToDoItem.PersistingEvent.class
)
public class ToDoItem {

    public static class PersistingEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectPersistingEvent<ToDoItem> { }

    // ...
}
persisted example

For example:

@DomainObjectLayout(
    persistedLifecycleEvent=ToDoItem.PersistedEvent.class
)
public class ToDoItem {

    public static class PersistedEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectPersistedEvent<ToDoItem> { }

    // ...
}
loaded example

For example:

@DomainObjectLayout(
    loadedLifecycleEvent=ToDoItem.LoadedEvent.class
)
public class ToDoItem {
    public static class LoadedEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectLoadedEvent<ToDoItem> { }
    ...
}
updating example

For example:

@DomainObjectLayout(
    updatingLifecycleEvent=ToDoItem.UpdatingEvent.class
)
public class ToDoItem {

    public static class UpdatingEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectUpdatingEvent<ToDoItem> { }

    // ...
}
updated example

For example:

@DomainObjectLayout(
    updatedLifecycleEvent=ToDoItem.UpdatedEvent.class
)
public class ToDoItem {

    public static class UpdatedEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectUpdatedEvent<ToDoItem> { }

    // ...
}
removing example

For example:

@DomainObjectLayout(
    removingLifecycleEvent=ToDoItem.RemovingEvent.class
)
public class ToDoItem {

    public static class RemovingEvent extends
        org.apache.causeway.applib.events.lifecycle.ObjectRemovingEvent<ToDoItem> { }

    // ...
}

Auditing

The entityChangePublishing() element indicates that if the object is modified, then each of its changed properties should be submitted to the registered EntityPropertyChangeSubscriber(s).

The default value for the element is AS_CONFIGURED, meaning that the causeway.applib.annotation.domain-object.entity-change-publishing configuration property is used to determine the whether the action is audited:

  • all

    all changed properties of objects are audited

  • none

    no changed properties of objects are audited

If there is no configuration property in application.properties then auditing is automatically enabled for domain objects.

This default can be overridden on an object-by-object basis; if entityChangePublishing() is set to ENABLED then changed properties of instances of the domain class are audited irrespective of the configured value; if set to DISABLED then the changed properties of instances are not audited, again irrespective of the configured value.

For example:

@DomainObject(
    entityChangePublishing=Publishing.ENABLED  (1)
)
public class Customer {
    ...
}
1 because set to enabled, will be audited irrespective of the configured value.

Reference Data

Some domain classes are immutable to the user, and moreover have only a fixed number of instances. Often these are "reference" ("standing") data, or lookup data/pick lists. Typical examples could include categories, countries, states, and tax or interest rate tables.

Where the number of instances is relatively small, ie bounded, then the bounding() element can be used as a hint. For such domain objects the framework will automatically allow instances to be selected; Web UI (Wicket viewer) displays these as a drop-down list.

For example:

@DomainObject(
    bounding=Bounding.BOUNDED,
    editing=Editing.DISABLED        (1)
)
public class Currency {
    ...
}
1 This attribute is commonly combined with editing=DISABLED to enforce the fact that reference data is immutable

There is nothing to prevent you from using this attribute for regular mutable entities, and indeed this is sometimes worth doing during early prototyping. However, if there is no realistic upper bound to the number of instances of an entity that might be created, generally you should use autoComplete…​() supporting method or the @DomainObject#autoCompleteRepository attribute instead.

Auto-complete

The autoCompleteRepository() element nominates a single method on a domain service as the fallback means for looking up instances of the domain object using a simple string.

For example, this might search for a customer by their name or number. Or it could search for a country based on its ISO-3 code or user-friendly name.

If you require additional control - for example restricting the returned results based on the object being interacted with - then use the autoComplete…​() supporting method instead.

For example:

@DomainObject(
    autoCompleteRepository=CustomerRepository.class
)
public class Customer {
   ....
}

where:

@DomainService
public class CustomerRepository {
    List<Customer> autoComplete(String search);  (1)
    ...
}
1 is assumed to be called "autoComplete", and accepts a single string

(As noted above) the method invoked on the repository is assumed to be called "autoComplete". The optional autoCompleteMethod() element allows the name of this method to be overridden.

For example:

@DomainObject(
    autoCompleteRepository=Customers.class,
    autoCompleteAction="findByName"
)
public class Customer {
   ....
}

where in this case findByName might be an existing action already defined:

@DomainService
public class Customers {

    @Action(semantics=SemanticsOf.SAFE)
    public List<Customer> findByName(
        @MinLength(3)                       (1)
        @ParameterLayout(named="name")
        String name);
    ...
}
1 end-user must enter minimum number of characters to trigger the query
The autocomplete "action" can also be a regular method, annotated using @Programmatic. That is, it does not need to be part of the metamodel:

+

@DomainService
public class Customers {
    @Programmatic
    public List<Customer> findByName(
        @MinLength(3)
        String name);
    ...
}

Mixin method

The mixinMethod() element specifies the name of the method to be treated as a "reserved" method name, meaning that the mixin’s name should instead be inferred from the mixin’s type.

For example:

@DomainObject
public class Customer {

    @DomainObject(
        nature=Nature.MIXIN,                                (1)
        mixinMethod="execute"                               (2)
    )
    public class placeOrder {                               (3)

        public Customer execute(Product p, int quantity) {  (4)
            // ...
        }
        public String disableExecute() {
            // ...
        }
        public String validate0Execute() {
            // ...
        }
    }
)
1 This is a mixin.
Alternatively, could have used @Action.
2 This mixin is using a non-standard method.
3 a trick - this is not a static class, so the java compiler will create the 1-arg constructor automatically
4 Same name as the specified mixinMethod. Alternatively, when using @Action then method’s name defaults to act.

This allows all mixins to follow a similar convention, with the name of the mixin inferred entirely from its type ("placeOrder").

When invoked programmatically, the code reads:

someCustomer.new placeOrder().execute(someProduct, 3);

See also