javaisis

ISIS: Moving from deprecated @Action(invokeOn=...) to @Action(associateWith=...)


I am working on a project using ISIS 1.16.2. I have a superclass, called ConfigurationItem, which has some common properties (name, createdTimestamp etc.). For example it has a delete action method, annotated with @Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION, ...), which I need to be callable from entitys detail view as well as from collection views with selection boxes.

Example:

public class ConfigurationItem {

    @Action(
            invokeOn = InvokeOn.OBJECT_AND_COLLECTION,
            semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
            domainEvent = DeletedDomainEvent.class)
    public Object delete() {
        repositoryService.remove(this);
        return null;
    }

    // ...
}

public class ConfigurationItems {

    @Action(semantics = SemanticsOf.SAFE)
    public List<T> listAll() {
        return repositoryService.allInstances(<item-subclass>.class);
    }

    // ...
}

This works pretty well but the "invokeOn" annotation is now deprecated. The JavaDoc says that one should switch to @Action(associateWith="...") but I don't know how to transfer the semantics of 'InvokeOn' since I have no collection field for reference. Instead I only have the collection of objects returned by the database retrieve action.

My question is: How do I transfer the deprecated @Action(invokeOn=...) semantics to the new @Action(associateWith="...") concept for collection return values with no backed property field?

Thanks in advance!


Solution

  • Good question, this obviously isn't explained well enough in the Apache Isis documentation.

    The @Action(invokeOn=InvokeOn.OBJECT_AND_COLLECTION) has always been a bit of a kludge, because it involves invoking an action against a standalone collection (which is to say, the list of object returned from a previous query). We don't like this because there is no "single" object to invoke the action on.

    When we implemented that feature, the support for view models was nowhere near as comprehensive as it now is. So, our recommendation now is, rather than returning a bare standalone collection, instead wrap it in a view model which holds the collection.

    The view model then gives us a single target to invoke some behaviour on; the idea being that it is the responsibility of the view model to iterate over all selected items and invoke an action on them.

    With your code, we can introduce SomeConfigItems as the view model:

    @XmlRootElement("configItems")
    public class SomeConfigItems {
    
        @lombok.Getter @lombok.Setter
        private List<ConfigurationItem> items = new ArrayList<>();
    
        @Action(
            associateWith = "items",  // associates with the items collection
            semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
            domainEvent = DeletedDomainEvent.class)
        public SomeConfigItems delete(List<ConfigurationItem> items) {
            for(ConfigurationItem item: items) {
               repositoryService.remove(item);
            }
            return this;
        }
        // optionally, select all items for deletion by default
        public List<ConfigurationItem> default0Delete() { return getItems(); }
    
        // I don't *think* that a choices method is required, but if present then 
        // is the potential list of items for the argument
        //public List<ConfigurationItem> choices0Delete() { return getItems(); }
    }
    

    and then change the ConfigurationItems action to return this view model:

    public class ConfigurationItems {
    
        @Action(semantics = SemanticsOf.SAFE)
        public SelectedItems listAll() {
            List<T> items = repositoryService.allInstances(<item-subclass>.class);
            return new SelectedItems(items);
        }
    }
    

    Now that you have a view model to represent the output, you'll probably find other things you can do with it.

    Hope that makes sense!