Supporting Methods

Supporting methods are those that are associated with properties, collections and actions, providing additional imperative business rule checking and behaviour to be performed when the user interacts with those object members.

This association is performed by name matching. Thus, a property called "firstName", derived from a method getFirstName() may have supporting methods hideFirstName(), disableFirstName() and validateFirstName(). Supporting methods are, therefore, each characterized by their own particular prefix.

The table below lists the method prefixes that are recognized as part of Apache Isis' default programming model.

Table 1. Method Prefixes
Prefix Object Property Collection Action Action
Param
Description

autoComplete…​()

Y

Y

Return a list of matching elements for a property or an action parameter.

Alternatively, can specify for a class using @DomainObject
#autoCompleteRepository

See also choices…​()

choices…​()

Y

Y

Provide list of choices for a property or action parameter.

default…​()

Y

Y

Default value for a property or an action parameter.

disable…​()

Y

Y

Y

Y

Disables (makes read-only) a property, an action or (dynamically) an action parameter.

get…​()

Y

Y

Access the value of a property or collection.

See also set…​().

hide…​()

Y

Y

Y

Y

Hides a property, a collection, an action or (dynamically) an action parameter.

set…​()

Y

Sets the value of a property.

validate…​()

Y

Y

Validate the proposed value of a property or an action parameter.

Using name matching to associate supporting methods generally works very well, but of course if an object member’s method is renamed, there’s always the risk that the developer forgets to rename the supporting method; the supporting methods become "orphaned".

Apache Isis checks for this automatically, and will fail-fast (fail to boot) if any orphaned methods are located. A suitable error message is logged so that the issue can be easily diagnosed.

autoComplete…​()

The autoComplete…​() supporting method is called for action parameters and for properties to find objects from a drop-down list box. The use case is when the number of candidate objects is expected to be large, so the user is required to enter some characters to narrow the search down.

If the number of candidate objects is comparatively small, then use choices…​() supporting method instead.

The signature of the supporting method depends on whether it is for a parameter or a property.

Parameters

For an action parameter in (0-based) position N, and of type T, the signature is:

public List<T> autoCompleteNXxx(String search) { /* ... */ }

It is also valid to return T[], a Set<T> or a Collection<T>.

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private Product product;

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private int quantity;

    @Action(semantics=SemanticsOf.IDEMPOTENT)
    public ShoppingCartItem updateProduct(
                                final Product product,
                                final int quantity) {
        // ...
    }
    public Collection<Product> autoComplete0UpdateProduct(  (1)
                                    @MinLength(3)           (2)
                                    final String search ) {
        // ...
    }

    // ...
}
1 product is the 0th argument of the action.
2 the @MinLength annotation specifies the minimum number of characters that must be entered before a search is performed for matching objects

Properties

For a property of type T, the signature is:

public List<T> autoCompleteXxx(String search) { /* ... */ }

(As for action parameters) it is also valid to return T[], a Set<T> or a Collection<T>.

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {
    @Getter @Setter
    private Product product;

    public Collection<Product> autoCompleteProduct(
                                @MinLength(3)           (1)
                                final String search ) {
        ...
    }
    ...
}
1 the @MinLength annotation specifies the minimum number of characters that must be entered before a search is performed for matching objects

choices…​()

The choices…​() supporting method is called for both action parameters and for properties, to find objects from a drop-down list box. Unlike autoComplete…​(), the use case is when the number of objects is comparatively small and can be selected from a drop-down without any additional filtering.

The signature of the supporting method depends on whether it is for an action parameter or a property.

Parameters

For an action parameter in (0-based) position N, and of type T, the signature is:

public Collection<T> choicesNXxx() {
    // ...
}

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private Product product;

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private int quantity;

    @Action(semantics=SemanticsOf.IDEMPOTENT)
    public ShoppingCartItem updateProduct(
                                Product product,
                                final Integer quantity) {
        // ...
    }
    public Collection<Integer> choices1UpdateProduct() {
        return Arrays.asList(1,2,3,5,10,25,50,100);
    }

    // ...
}

Dependent Choices

Action parameters also support the notion of dependent choices, whereby the list of choices is dependent upon the value of some other argument.

For example, consider a todo app whereby ToDoItems are categorized and then can also be subcategorized:

dependent

If implemented as a mixin contributed action, the code would look something like:

public class ToDoItem {

    @Action(semantics = SemanticsOf.IDEMPOTENT)
    @ActionLayout(
            describedAs = "Update category and subcategory"
    )
    public class updateCategory {
        public ToDoItem act(
                final Category category,
                @Nullable
                final Subcategory subcategory) {
            item.setCategory(category);
            item.setSubcategory(subcategory);
            return ToDoItem.this;
        }
        public List<Subcategory> choices1UpdateCategory(    (1)
                final Category category) {                  (2)
            return Subcategory.listFor(category);
        }
    }

}
1 "choices" prefix, Nth param
2 the category selected

Dependent choices are not restricted to enums, however. Going back to the shopping cart example shown above, the choices for the quantity parameter could be dependent upon the selected Product:

public class ShoppingCartItem {
    ...
    @Action(semantics=SemanticsOf.IDEMPOTENT)
    public ShoppingCartItem updateProduct(
                                Product product,
                                final Integer quantity) {
        // ...
    }
    public Collection<Integer> choices1UpdateProduct(Product product) {
        return productService.quantityChoicesFor(product);               (1)
    }
    ...
}
1 productService is a (fictitious) injected service that knows what the quantity choices should be for any given product

Properties

For a property of type T, the signature is:

public Collection<T> choicesXxx() { /* ... */ }

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {

    @Property(editing = Editing.ENABLED)
    @Getter @Setter
    private Product product;

    public Collection<Product> choicesProduct() {
        // ...
    }

    // ...
}

default…​()

The default…​() supporting method is called for action parameters to return the initial argument value. This may be some sensible default (eg today’s date, or 0 or 1), or — for an action that is modifying the state of an object — might default to the current value of a corresponding property.

The method is also called for properties in the case when an object is newly instantiated using RepositoryService#detachedEntity(…​) or FactoryService#create(…​). This is a much less common use case. If a default is not specified then properties are initialized to a default based on their type (eg 0 or false).

The signature of the supporting method depends on whether it is for an action parameter or a property.

Parameters

For an action parameter in (0-based position n), and of type T, the signature is:

public T defaultNXxx() {
    // ...
}

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private Product product;

    @Property(editing=Editing.DISABLED)
    @Getter @Setter
    private int quantity;

    @Action(semantics=SemanticsOf.IDEMPOTENT)
    public ShoppingCartItem updateProduct(
                                Product product,
                                final Integer quantity) {
        // ...
    }

    public Product default0UpdateProduct() {    (1)
        return getProduct();
    }
    public int default1UpdateProduct() {        (2)
        return getQuantity();
    }

    // ...
}
1 default the 0-th parameter using the current value of the product property
2 default the 1-th parameter using the current value of the quantity property

If implemented as a mixin contributed action, the code would look something like:

Defaults are also supported (of course) for contributed actions. For example, here is a contributed action for updating category/subcategory of a ToDoItem:

public class ToDoItem {

    @Action(semantics = SemanticsOf.IDEMPOTENT)
    @ActionLayout(
            describedAs = "Update category and subcategory"
    )
    public class updateCategory {
        public ToDoItem act(
                final Category category,
                @Nullable
                final Subcategory subcategory) {
            item.setCategory(category);
            item.setSubcategory(subcategory);
            return ToDoItem.this;
        }
        // ...
        public Category default1UpdateCategory(        (1)
                final Categorized item) {
            return ToDoItem.this.getCategory();
        }
        public Subcategory default2UpdateCategory(     (2)
                final Categorized item) {
            return ToDoItem.this.getSubcategory();
        }
    }
}
1 defaults the 0-th parameter using the item’s category property
2 defaults the 1-th parameter using the item’s subcategory property

Dependent Defaults

Action parameters also support the notion of dependent defaults, whereby the value of a default is dependent upon the value of some other previous argument.

In this example we can default the gross argument based on the previous net and vat arguments:

public int add(final Integer net, final Integer vat, final Integer gross) {
    return net + vat;
}

public Integer default2Add(final Integer net, final Integer val) {
    if(net == null || val == null) { return null; }
    return net + val;
}

Properties

For a property of type T, the signature is:

public T defaultXxx() {
    // ...
}

For example:

import lombok.Getter;
import lombok.Setter;

public class ShoppingCartItem {

    @Getter @Setter
    private int quantity;

    public int defaultQuantity() {
        return 1;
    }

    // ...
}

Alternatives

There are, in fact, two other ways to set properties of a newly instantiated object to default values.

The first is to use the created() callback, called by the framework when RepositoryService#detachedEntity(…​) or FactoryService#create(…​) is called. This method is called after any services have been injected into the domain object.

The second is more straightforward: simply initialize properties in the constructor. However, this cannot use any injected services as they will not have been initialized.

disable…​()

The disable…​() supporting method is called for properties, actions and action parameters. It allows the modification of the property to be vetoed (ie made read-only) and to prevent the invocation of the action ("grey it out"). For parameters, it disables the entry into the parameter; this is for actions with dependencies between the parameters.

Typically modification/invocation is vetoed based on the state of the domain object being interacted with, though it could be any reason at all (eg the current date/time of the interaction, or the state of some other related data such as stock levels, or the identity of the calling user).

The reason for vetoing a modification/invocation is normally returned as a string. However, Apache Isis' i18n support extends this so that reasons can be internationalized.

The signature of the supporting method is generally:

public String disableXxx() {
    // ...
}

where the returned string is the reason the property cannot be edited, or the action invoked.

For i18n, the supporting method returns a TranslatableString:

public TranslatableString disableXxx() {
    // ...
}

The returned string is then automatically translated to the locale of the current user.

To disable a property:

import lombok.Getter;
import lombok.Setter;

public class Customer {

    @Getter @Setter
    private boolean blacklisted;

    @Getter @Setter
    private BigDecimal creditLimit;

    public String disableCreditLimit() {
        return isBlacklisted()
                    ? "Cannot change credit limit for blacklisted customers"
                    : null;
    }

    // ...
}

Or, to disable an action:

import lombok.Getter;
import lombok.Setter;

public class Customer {

    @Getter @Setter
    private boolean blacklisted;

    @Action
    public Order placeOrder(
                    final Product product,
                    final int quantity) {
        // ...
    }
    public String disablePlaceOrder() {
        return isBlacklisted()
                    ? "Blacklisted customers cannot place orders"
                    : null;
    }

    // ...
}

It is also possible to disable an action parameter, so that it is disabled (greyed out) based on the value of earlier parameters. For example:

public class ToDoItem {

public class ToDoItem {

    @Action(semantics = SemanticsOf.IDEMPOTENT)
    @ActionLayout(
            describedAs = "Update category and subcategory"
    )
    public Product categorize(
                    Category category,
                    Subcategory subcategory) {
        // ...
    }
    public String disable1Categorize(Category category) {
        return category == null || category.hasSubcategories()
                ? null
                : "The selected category has no subcategories";
    }

    // ...
}

get…​()

The get…​() prefix is simply the normal JavaBean getter prefix that denotes properties or collections.

When Apache Isis builds its metamodel, it first searches for the getter methods, characterizing them as either properties or collections based on the return type. It then refines the metamodel based on the presence of annotations and supporting methods.

All remaining public methods (that do not use one of the Apache Isis prefixes) are interpreted as actions.

Any methods "left over" that do use one of the Apache Isis prefixes, are interpreted to be orphaned. Apache Isis "fails-fast" and will not boot, instead printing an error message to the log so that the issue can be easily diagnosed.

See also set…​().

hide…​()

The hide…​() supporting method is called for properties, collections, actions and action parameters. It allows the property/collection to be completely hidden from view.

It’s comparatively rare for properties or collections to be imperatively hidden from view, but actions are sometimes hidden or shown visible (as opposed to being just disabled, ie greyed out).

The signature of the supporting method is simply:

public boolean hideXxx() {
    // ...
}

Returning true will hide the property, collection or action, returning false leaves it visible.

For example, to hide a property:

import lombok.Getter;
import lombok.Setter;

public class Customer {

    @Getter @Setter
    private boolean blacklisted;

    @Getter @Setter
    private BigDecimal creditLimit;
    public boolean hideCreditLimit() {
        return isBlacklisted();
    }

    // ...
}

Or, to hide an action:

import lombok.Getter;
import lombok.Setter;

public class Customer {

    @Getter @Setter
    private boolean blacklisted;

    public Order placeOrder(
            final Product product,
            final int quantity) {
        // ...
    }
    public boolean hidePlaceOrder() {
        return isBlacklisted();
    }

    // ...
}

It is also possible to hide an action parameter, based on the value of some other earlier parameter: Each hideNXxx() method can declare parameters for the previous N-1 parameters, though it need not declare them all.

For example:

public class Order {
    public Order shipTo(
            boolean sameAsBillingAddress,
            String addressLine1,
            String addressLine2,
            String addressCity,
            String addressPostalCode,
            String addressCountry ) {
        // ...
    }
    public boolean hide1ShipTo(boolean same) {
        return same;
    }
    public boolean hide2ShipTo(boolean same) {
        return same;
    }
    public boolean hide3ShipTo(boolean same) {
        return same;
    }
    public boolean hide4ShipTo(boolean same) {
        return same;
    }
    public boolean hide5ShipTo(boolean same) {
        return same;
    }
}

In this case, the user can use the shipTo(…​) action to specify where to ship the Order to. However, if they check the first boolean parameter (ie, to ship the Order to the billing address already held on file), then the remaining parameters will all be hidden.

set…​()

The set…​() prefix is simply the normal JavaBean setter prefix that denotes writeable properties or collections.

See also get…​().

validate…​()

The validate…​() supporting method is called for properties, actions and action parameters.

TODO: v2 - need to document