Properties, Collections & Actions

Introduction

Domain entities and view models hold state in terms of properties and collections; both of these and also domain services define behaviour in terms of actions.

Properties and Collections

Both properties and collections are defined by getters, the difference being that the return type for properties is a scalar while for collections it is a collection of elements. Additionally, the scalar property can be a value object (such as int, date or String), or could be a reference to another entity or perhaps view model, whereas for a collection the element types may only be reference types, not value types.

Regarding mutability: if a property is modifiable then there will also be a setter; collections however are always immutable and so there should be no setter.

So for example:

public class Customer {

    @Property                                           (1)
    @PropertyLayout                                     (2)
    @Getter @Setter                                     (3)
    private String lastName;

    @Collection                                         (4)
    @CollectionLayout                                   (5)
    @Getter                                             (6)
    private SortedSet<Order> orders = new TreeSet<>;    (7)
    ...
}
1 Depending on configuration settings, the @Property annotation may or may not be required; its role is to define additional domain-layer semantics
2 The @PropertyLayout annotation defines additional presentation-layer hints; alternatively this metadata can be provided using the layout.xml file.
3 It’s commonplace to use Project Lombok to remove boilerplate. As there is a @Setter, this property may also editable (though this does also depend on configuration settings).
4 Depending on configuration settings, the @Collection annotation may or may not be required; its role is to define additional domain-layer semantics
5 The @CollectionLayout annotation defines additional presentation-layer hints; alternatively this metadata can be provided using the layout.xml file.
6 Collections are immutable, so no setter is required.
7 Typically entities implement Comparable, so SortedSet is typical. (For entities, the actual implementation will be replaced by the ORM).

From this the framework infers the Customer domain entity, which in turn has a lastName string property and an orders collection.

Actions

While properties and collections define the state held by a domain object (its "know what" responsibilities), actions define the object’s behaviour (its "know how-to" responsibilities).

An application that consists only of domain entities with just "know-what" responsibilities is pretty dumb: it requires that the end-user know the business rules and doesn’t modify the state of the domain objects such that they are invalid (for example, an "end date" being before a "start date"). Such applications are often called CRUD applications (create/read/update/delete).

In more complex domains, it’s not realistic/feasible to expect the end-user to have to remember all the different business rules that govern the valid states for each domain object. Actions allow those business rules to be encoded programmatically. Those actions can either be defined in the domain class itself, or can be contributed by way of a mixin.

The general philosophy for an Apache Causeway (naked objects) application is not to constrain the end-user in how they interact with the UI: it doesn’t attempt to define a rigid business process. However, it does aim to ensure that business rule invariants are maintained, that is that domain objects aren’t allowed to enter into an invalid state.

For simple domain applications, you may want to start prototyping only with properties, and only later introduce actions (representing the most common business operations). But an alternative approach, recommended for more complex applications, is actually to start the application with all properties non-editable. Then, as the end-user requires the ability to modify some state, there is a context in which to ask the question "why does this state need to change?" and "are their any side-effects?" (ie, other state that changes at the same time, or other behaviour that should occur). If the state change is simple, for example just being able to correct an invalid address, or adding a note or comment, then that can probably be modelled as a simple editable property. But if the state change is more complex, then most likely an action should be used instead.

Broadly speaking, actions are those public methods that do not represent properties or collections.

For example:

public class Customer {

    @Action                       (1)
    @ActionLayout                 (2)
    public Customer placeOrder(
        @Parameter                (3)
        @ParameterLayout          (4)
        Product p,
        @Parameter
        @ParameterLayout
        int quantity) {
        /* ... */
    }

    // ...
}
1 Depending on configuration settings, the @Action annotation may or may not be required; its role is to define additional domain-layer semantics
2 The @ActionLayout annotation defines additional presentation-layer hints; alternatively this metadata can be provided using the layout.xml file.
3 The @Parameter annotation defines additional domain-layer semantics. This is optional though often required.
4 The @ParameterLayout annotation defines additional presentation-layer hints

From this the framework infers a the placeOrder action for object.

Using meta-annotations

Often the action parameters correspond to properties of the domain object. For example, an action to change a customer’s lastName property would likely define a newLastName parameter whose type and semantics would correspond to that of the property.

Defining a @LastName meta-annotation can be one way to place both the @Property/@Parameter and @PropertyLayout/@ParameterLayout` side-by-side; both the lastName property and newLastName parameter would then be annotated with @LastName.

Properties

A property is an instance variable of a domain object, of a scalar type, that holds some state in either a domain entity or a view model.

For example, a Customer's firstName would be a property, as would their accountCreationDate that they created their account. All properties have at least a "getter" method, and most properties have also a "setter" method (meaning that they are mutable). Properties that do not have a setter method are derived properties, and so are not persisted.

Formally speaking, a property is simply a regular JavaBean getter, returning a scalar value recognized by the framework. Most properties (those that are editable/modifiable) will also have a setter and, if persisted, a backing instance field. And most properties will also have a number of annotations:

  • Apache Causeway defines its own @Property annotation for capturing domain semantics. It also provides a @PropertyLayout for UI hints (though the information in this annotation may instead be provided by a supplementary .layout.xml file)

  • the properties of domain entities are usually also annotated with ORM annotation(s).

    For JPA/EclipseLink this would be javax.persistence.Column, for JDO/DataNucleus it is @javax.jdo.annotations.Column annotation. For property references, there may be other annotations to indicate whether the reference is bidirectional. It’s also possible (using annotations) to define a link table to hold foreign key columns.

  • for the properties of view models, then JAXB annotations such as @XmlElement may be present

Apache Causeway recognises some of these annotations from the persistence layer and infers some domain semantics from them (for example, the maximum allowable length of a string property).

Since writing getter and setter methods adds quite a bit of boilerplate, it’s common to use Project Lombok to code generate these methods at compile time (using Java’s annotation processor) simply by adding the @lombok.Getter and @lombok.Setter annotations to the field. The SimpleApp starter app uses this approach.

Value vs Reference Types

Properties can be either a value type (strings, int, date and so on) or be a reference to another object (for example, an Order referencing the Customer that placed it).

For example, to map a string value type:

import lombok.Getter;
import lombok.Setter;

@Getter @Setter        (1)
private String notes;
1 using Project Lombok annotations to reduce boilerplate

You could also add the @Property annotation if you wished:

import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private String notes;

Although in this case it is not required (none of its attributes have been set).

Or to map a reference type:

import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private Customer customer;

It’s ok for a view model to reference other view models and also to reference domain entities. However, it isn’t valid for a domain entity to hold a reference to view model, because the ORM will not know how to persist the view model object.

For further details on mapping associations using JDO/DataNucleus, see their mapping guide.

Optional Properties

(For domain entities) JDO/DataNucleus' default is that a property is assumed to be mandatory if it is a primitive type (eg int, boolean), but optional if a reference type (eg String, BigDecimal etc). To override optionality in JDO/DataNucleus the @Column(allowsNull="…​") annotations is used.

Apache Causeway on the other hand assumes that all properties (and action parameters, for that matter) are mandatory, not optional. These defaults can also be overridden using Apache Causeway' own annotations, specifically @Property(optionality=…​), or (because it’s much less verbose) using @javax.annotation.Nullable.

These different defaults can lead to incompatibilities between the two frameworks. To counteract that, Apache Causeway also recognizes and honours JDO’s @Column(allowsNull=…​).

For example, you can write:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(allowsNull="true")
@Property
@Getter @Setter
private LocalDate date;

rather than the more verbose:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(allowsNull="true")
@Property(optionality=Optionality.OPTIONAL)
@Getter @Setter
private LocalDate date;

The framework will search for any incompatibilities in optionality (whether specified explicitly or defaulted implicitly) between Apache Causeway' defaults and DataNucleus, and will refuse to boot if any are found.

Editable Properties

Apache Causeway provides the capability to allow individual properties to be modified. This is specified using the @Property(editing=…​) attribute.

For example:

import lombok.Getter;
import lombok.Setter;

@Property(editing = Editing.ENABLED)
@Getter @Setter
private String notes;

If this is omitted then whether editing is enabled or disabled is inherited from the domain object level, @DomainObject#editing().

If that isn’t specified (or is set to "AS_CONFIGURED"), then the configuration is taken from the application.properties configuration file:

And, finally, if there is no configuration property set at all, then the default is for editing to be DISABLED.

For entities, editable properties are not necessarily persistable. In such a case the setter would not write to a field, but would (presumably) mutate the object in some other way. In such a case you will need a getter and a setter, but the property annotated for the ORM as non-persistent. (If using JDO/DataNucleus, this is done using @NotPersistent).

For example:

import javax.inject.Inject;
import javax.jdo.annotations.Column
import javax.jdo.annotations.NotPersistent;
import lombok.Getter;
import lombok.Setter;

@javax.jdo.annotations.NotPersistent
@Property(editing=Editing.ENABLED)
public String getAddress() {
    return addressService.toAddress( getLatLong() );                (1)
}
public void setAddress(String address) {
    setLatLong(addressService.toLatLong(address));
}

@Column
@Programmatic
@Getter @Setter
private String latLong;                                             (2)

@Inject
AddressService addressService;                                      (3)
1 the representation of the address, in human readable form, eg "10 Downing Street, London, UK"
2 the lat/long representation of the address, eg "51.503363;-0.127625". Excluded from the Apache Causeway metamodel.
3 an injected service that can convert to/from address and latLong.

Ignoring Properties

By default Apache Causeway will automatically render all properties in the Wicket UI or in the REST API. To get Apache Causeway to ignore a property (exclude it from its metamodel), annotate the getter using @Programmatic.

Similarly, for the JDO/DataNucleus ORM, ignore a property using the @javax.jdo.annotations.NotPersistent annotation. This is independent of Apache Causeway; in other words that property will still be rendered in the UI (unless also annotated with @Programmatic).

For view models, you can tell JAXB to ignore a property using the @javax.xml.bind.annotation.XmlTransient annotation. Again, this is independent of Apache Causeway.

You can also suppress a property from the UI using @Property#hidden() or indeed @PropertyLayout#hidden(). However, this doesn’t exclude the property from the metamodel.

Derived Properties

Derived properties are those with a getter but no setter. These will still be rendered in the UI, but they will be read-only (not editable).

For entities these may or may not be persisted; that depends on whether the ORM annotations are specified on the field or on the property.

Data types (ORM considerations)

This section shows specific considerations for various datatypes, in particular how to annotate them for the ORM.

This section covers the JDO/DataNucleus object store.

Strings (Length)

By default JDO/DataNucleus will map string properties to a VARCHAR(255). To limit the length, use the @Column(length=…​) annotation.

For example:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(length=50)
@Property
@Getter @Setter
private String firstName

This is a good example of a case where Apache Causeway infers domain semantics from the JDO annotation.

JODA Dates

Apache Causeway' bundles DataNucleus' built-in support for Joda LocalDate and LocalDateTime datatypes, meaning that entity properties of these types will be persisted as appropriate data types in the database tables.

It is, however, necessary to annotate your properties with @javax.jdo.annotations.Persistent, otherwise the data won’t actually be persisted. See the JDO docs for more details on this.

Moreover, these datatypes are not in the default fetch group, meaning that JDO/DataNucleus will perform an additional SELECT query for each attribute. To avoid this extra query, the annotation should indicate that the property is in the default fetch group.

For example:

import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.Column;
import org.joda.time.LocalDate;

@Persistent(defaultFetchGroup="true")
@Column(allowsNull="true")
@Property
@Getter @Setter
private LocalDate dueBy;

BigDecimals (Precision)

Working with java.math.BigDecimal properties takes a little care due to scale/precision issues.

For example, suppose we have:

import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private BigDecimal impact;

Without any constraints, the length of the corresponding column is database specific. For example, with HSQL JDO/DataNucleus maps to a column NUMERIC(19); no decimal digits are admitted. (Further details here).

What this implies is that, when a record is inserted, a log entry similar to this one appears:

INSERT INTO ENTITY(..., IMPACT, ....) VALUES (...., 0.5, ....)

But when that same record is retrieved, the log will show that a value of "0" is returned, instead of 0.5.

The solution is to explicitly add the scale to the field like this:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(scale=2)
@Getter @Setter
private BigDecimal impact;

In addition, you should also set the scale of the BigDecimal, using setScale(scale, roundingMode).

More information can be found here and here.

Blobs

Apache Causeway configures JDO/DataNucleus so that the properties of type org.apache.causeway.applib.value.Blob and org.apache.causeway.applib.value.Clob can also be persisted.

As for Joda dates, this requires the @javax.jdo.annotations.Persistent annotation. However, whereas for dates one would always expect this value to be retrieved eagerly, for blobs and clobs it is not so clear cut.

For example:

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name"),
    @Column(name = "attachment_mimetype"),
    @Column(name = "attachment_bytes",
            jdbcType="BLOB", sqlType = "LONGVARBINARY")
})
@Property(optionality = Optionality.OPTIONAL)
@Getter @Setter
private Blob attachment;

The three @Column annotations are required because the mapping classes that Apache Causeway provides (CausewayBlobMapping and CausewayClobMapping) map to 3 columns. (It is not an error to omit these @Column annotations, but without them the names of the table columns are simply suffixed _0, _1, _2 etc.

If the Blob is mandatory, then use:

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name", allowsNull="false"),
    @Column(name = "attachment_mimetype", allowsNull="false"),
    @Column(name = "attachment_bytes",
            jdbcType="BLOB", sqlType = "LONGVARBINARY", allowsNull="false")
})
@Property(optionality = Optionality.MANDATORY )
@Getter @Setter
private Blob attachment;

If specifying a sqlType of "LONGVARBINARY" does not work, try instead "BLOB". There can be differences in behaviour between JDBC drivers.

Clobs

Mapping Clobs works in a very similar way to Blobs, but the jdbcType and sqlType attributes will, respectively, be CLOB and LONGVARCHAR:

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name"),
    @Column(name = "attachment_mimetype"),
    @Column(name = "attachment_chars",
            jdbcType="CLOB", sqlType = "LONGVARCHAR")
})
@Property( optionality = Optionality.OPTIONAL )
@Getter @Setter
private Clob doc;

If specifying a sqlType of "LONGVARCHAR" does not work, try instead "CLOB". There can be differences in behaviour between JDBC drivers.

Mapping to VARBINARY or VARCHAR

Instead of mapping to a sqlType of LONGVARBINARY (or perhaps BLOB), you might instead decide to map to a VARBINARY. The difference is whether the binary data is held "on-row" or as a pointer "off-row"; with a VARBINARY the data is held on-row and so you will need to specify a length.

For example:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(
        name = "attachment_bytes",
        jdbcTypr="BLOB", sqlType = "VARBINARY", length=2048
)
@Getter @Setter
private Blob image;

The same argument applies to LONGVARCHAR (or CLOB); you could instead map to a regular VARCHAR:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(
        name = "attachment_chars",
        sqlType = "VARCHAR", length=2048
)
@Getter @Setter
private Clob letter;

Support and maximum allowed length will vary by database vendor.

Collections

A collection is an instance variable of a domain object, of a collection type that holds references to other domain objects. For example, a Customer may have a collection of Orders).

It’s ok for a view model to reference both view model and domain entities. However, it isn’t valid for a domain entity to hold a reference to view model, because the ORM will not know how to persist the view model object.

Formally speaking, a collection is simply a regular JavaBean getter, returning a collection type (subtype of java.util.Collection). Most collections (those that are modifiable) will also have a setter and, if persisted, a backing instance field. And collections properties will also have a number of annotations:

  • Apache Causeway defines its own xref @Collection annotation for capturing domain semantics. It also provides a @CollectionLayout for UI hints (though the information in this annotation may instead be provided by a supplementary .layout.xml file)

  • the collections of domain entities are often annotated with ORM annotation(s). In the case of JDO/DataNucleus annotations this is most notably javax.jdo.annotations.Persistent. Other annotations can be used to specify if the association is bidirectional, and whether to define a link table or not to hold foreign key columns.

  • for the collections of view models, then JAXB annotations such as @javax.xml.bind.annotation.XmlElementWrapper and @javax.xml.bind.annotation.XmlElement will be present

Apache Causeway may recognise some of these annotations from the persistence layer infers some domain semantics from them.

Unlike properties, the framework does not allow collections to be "edited". Instead, actions can be written that will modify the contents of the collection as a side-effect. For example, a placeOrder(…​) action will likely add an Order to the Customer#orders collection.

Since writing getter and setter methods adds quite a bit of boilerplate, it’s common to use Project Lombok to code generate these methods at compile time (using Java’s annotation processor) simply by adding the @lombok.Getter and @lombok.Setter annotations to the field.

see the DataNucleus Mapping Guide for more in-depth coverage of this topic.

Mapping bidir 1:m

Bidirectional one-to-many collections are one of the most common types of associations between two entities:

Parent has many Children, each Child has one Parent.
Figure 1. Parent has many Children, each Child has one Parent.

In the parent object, the collection can be defined as:

import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

public class ParentObject
        implements Comparable<ParentObject>{

    @Persistent(
        mappedBy = "parent",                                               (1)
        dependentElement = "false"                                         (2)
    )
    @Collection                                                            (3)
    @Getter @Setter
    private SortedSet<ChildObject> children = new TreeSet<ChildObject>();  (4)

}
1 indicates a bidirectional association; the foreign key pointing back to the Parent will be in the table for ChildObject
2 disables cascade delete
3 (not actually required in this case, because no attributes are set, but acts as a useful reminder that this collection will be rendered in the UI by Apache Causeway)
4 uses a SortedSet (as opposed to some other collection type; discussion below)

while in the child object you will have:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

public class ChildObject
        implements Comparable<ChildObject> {    (1)

    @Column(allowsNull = "false")               (2)
    @Property(editing = Editing.DISABLED)       (3)
    @Getter @Setter
    private ParentObject parent;
}
1 implements Comparable because is mapped using a SortedSet
2 mandatory; every child must reference its parent
3 cannot be edited directly

Generally speaking you should use SortedSet for collection types (as opposed to Set, List or Collection). JDO/Datanucleus does support the mapping of these other types, but RDBMS are set-oriented, so using this type introduces the least friction.

Maps

While ORMs support java.util.Map as a collection type, maps are not supported by Apache Causeway.

If you do wish to use this collection type, then annotate the getter with @Programmatic so that it is ignored by the Apache Causeway framework.

Value vs Reference Types

While ORMs support collections/arrays of value types, such collections are not supported by Apache Causeway. Apache Causeway can (currently) only provide a UI for collections of references.

If you do wish to use collections of this type, then annotate the getter with @Programmatic so that it is ignored by the Apache Causeway framework.

As a workaround, if you want to visualize an array of value types in Apache Causeway, then one option is to wrap the value in a view model.

Derived Collections

A derived collection is simply a getter (no setter) that returns a java.util.Collection (or subtype).

While derived properties and derived collections typically "walk the graph" to associated objects, there is nothing to prevent the returned value being the result of invoking a repository (domain service) action.

For example:

public class Customer {
    ...
    public List<Order> getMostRecentOrders() {
        return orderRepo.findMostRecentOrders(this, 5);
    }
}

Actions

An action is a public method that is presented as a prompt form, and invoked upon a target object (or on a mixin contributing to the target object).

They allow the user to perform complex interactions with the domain object, and so raise the level of abstraction compared to requiring the user than simple CRUD-style operations.

For example, imagine a lease management system, where a lease is extended by creating a sibling lease term that starts on the same date that the original lease term ended. There are three operations here: set the end date of the original lease term, create the new lease term with relevant details copied over from the original lease term, and set the start date of the lease term correctly.

Instead, all of these operations can be combined into a single action, "renew". Through such means the ubiquitous language grows.

This is the reason why Apache Causeway' default is non-editable properties: to encourage this sort of "knowledge crunching".

You can find further discussion on when and why you should write actions earlier.

Defining actions

If the causeway.core.meta-model.introspector.policy configuration property is set to ANNOTATION_OPTIONAL or is left unset (the default), then any "left-over" public methods will be considered to be actions. These are methods that do not represent properties or collections, and that are not recognised as supporting methods (such as hideXxx() or disableXxx()).

If the policy is set to ANNOTATION_REQUIRED, then the @Action annotation must be applied for the method to be treated as an action. The @Action annotation is also used to specify additional domain semantics, for example regarding idempotency.

For example:

@Action(semantics=SemanticsOf.IDEMPOTENT)       (1)
public ShoppingBasket addToBasket(
        Product product,
        int quantity
        ) {
    ...
    return this;
}
1 @Action annotation indicates that this public method is an action..

If the code is compiled (javac) using the -parameters flag, then the name of the parameter in the metamodel uses the name of the parameter variable.

Otherwise, the type of the parameter is used. For the product parameter this is reasonable, but not so for the quantity parameter (which would by default show up with a name of "int". In such a case the @ParameterLayout annotation can be used to provide the UI hint.

The policy can also be set to ENCAPSULATION_ENABLED. This is similar to ANNOTATION_REQUIRED, but methods can have any visibility, including private. Supporting methods (hideXxx() and the like)) must be annotated with MemberSupport.

(Reference) Parameter types

Parameter types can be value types or reference types. In the case of primitive types, the end-user can just enter the value directly through the parameter field. In the case of reference types however (such as Product), a drop-down must be provided from which the end-user to select. This is done using either a supporting choices or autoComplete method. The "choices" is used when there is a limited set of options, while "autoComplete" is used when there are large set of options such that the end-user must provide some characters to use for a search.

For example, the addToBasket(…​) action shown above might well have an autocomplete supporting method :

@Action(semantics=SemanticsOf.IDEMPOTENT)
public ShoppingBasket addToBasket(
        Product product,
        @ParameterLayout(named="Quantity")
        int quantity
        ) {
    ...
    return this;
}
public List<Product> autoComplete0AddToBasket(              (1)
    @MinLength(3)                                           (2)
    String searchTerm) {
    return productRepository.find(searchTerm);              (3)
}
@javax.inject.Inject
ProductRepository productRepository;
1 Supporting autoComplete method.
The "0" in the name means that this corresponds to parameter 0 of the "addToBasket" action (ie Product). It is also required to return a Collection of that type.
2 The @MinLength annotation defines how many characters the end-user must enter before performing a search.
3 The implementation delegates to an injected repository service. This is typical.

Note that it is also valid to define "choices" and "autoComplete" for value types (such as quantity, above); it just isn’t as common to do so.

Removing boilerplate with autoCompleteRepository

To save having to define an autoCompleteNXxx(…​) method everywhere that a reference to a particular type (such as Product) appears as an action parameter, it is also possible to use the @DomainObject annotation on Product itself:

@DomainObject(
    autoCompleteRepository=ProductRepository.class          (1)
    autoCompleteMethod="find"                               (2)
)
public class Product ... {
    ...
}
1 Whenever an action parameter requiring a Product is defined, provide an autoComplete drop-down automatically
2 Use the "find" method of ProductRepository (rather than the default name of "autoComplete").
The referenced method can be @Programmatic - it doesn’t actually need to be an action in the metamodel.

Removing boilerplate with choices

If the number of available instances of the reference type is a small number (in other words, all of which could comfortably be shown in a drop-down) then instead the choicesNXxx() supporting method can be used. This too can be avoided by annotating the referenced class.

For example, suppose we have an action to specify the PaymentMethodType, where there are only 10 or so such (Visa, Mastercard, Amex, Paypal etc). We could define this as:

@Action
public Order payUsing(PaymentMethodType type) {
    ...
}

where PaymentMethodType would be annotated using:

@DomainObject(
    bounded=true                            (1)
)
public class PaymentMethodType ... {
    ...
}
1 only a small (ie "bounded") number of instances available, meaning that the framework should render all in a drop-down.

Collection Parameter types

Action parameters can also be collections of values (for example List<String>), or can be collections of references (such as List<Customer>).

For example:

@Action(semantics=SemanticsOf.IDEMPOTENT)
public ShoppingBasket addToBasket(
        List<Product> products,
        int quantity
        ) {
    // ...
    return this;
}
public List<Product> autoComplete0AddToBasket(              (1)
                        @MinLength(3) String searchTerm) {
    return ...
}

As the example suggests, any collection parameter type must provide a way to select items, either by way of a "choices" or "autoComplete" supporting method or alternatively defined globally using @DomainObject on the referenced type (described above).

Optional Parameters

Either the @Nullable annotation or the @Parameter#optionality annotation/attribute can be used to indicate that a parameter can be left blank.

For example:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;
import org.joda.time.LocalDate;

@Action(semantics=SemanticsOf.IDEMPOTENT)
public Order invoice(
                PaymentMethodType paymentMethodType,
                @Nullable                                      (1)
                @ParameterLayout(named="Ship no later than")
                LocalDate shipBy) {
    ...
    setShipBy(shipBy)
    return this;
}

@Column(allowsNull="true")                                     (2)
@Property
@Getter @Setter
private LocalDate shipBy;
1 Specifies the parameter is optional.
2 Specifies the corresponding property is optional.

Note that this uses an ORM-specific mechanism to specify the same semantics (in this case, using JDO/DataNucleus' @Column#allowsNull().)

String Parameters (Length)

The @Parameter#maxLength annotation/attribute is used to specify the maximum number of characters allowed for a string parameter.

For example:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

public Customer updateName(
                @Parameter(maxLength=50)                (1)
                @ParameterLayout(named="First name")
                String firstName,
                @Parameter(maxLength=50)
                @ParameterLayout(named="Last name")
                String lastName) {
    setFirstName(firstName);
    setLastName(lastName);
    return this;
}

@Column(length=50)                                      (2)
@Getter @Setter
private String firstName;

@Column(length=50)
@Getter @Setter
private String lastName;
1 Specifies the parameter length using @Parameter#maxLength annotation
2 Specifies the length of a corresponding property.

Note that this uses an ORM-specific annotation (in this case, @Column#length() annotation

Incidentally, note in the above example that the new value is assigned to the properties using the setter methods; the action does not simply set the instance field directly.

This is important, because it allows the ORM to keep track that this instance variable is "dirty" and so needs writing to the database table before the transaction completes.

BigDecimals (Precision)

The @javax.validation.constraints.Digits#fraction annotation/attribute is used to specify the scale/precision of decimals.

For example:

import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

public Order updateDiscount(
                @javax.validation.constraints.Digits(fraction=2)    (1)
                @ParameterLayout(named="Discount rate")
                String discountRate) {
    setDiscountRate(discountRate);
    return this;
}

@Column(scale=2)                                                    (2)
@Getter @Setter
private BigDecimal discountRate;
1 Specifies the parameter precision using @Digits#fraction.
2 Specifies the corresponding property precision.

Note that this uses an ORM-specific annotation (in this case, @Column#scale