You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/04/25 19:36:09 UTC

[isis] 05/08: ISIS-2340: share logic of ObjectAdapterAccessHelper/UpdateHelper (5)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 98a2ff1fbcdaea0390ad26344159e5aadcfdae6d
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Apr 24 12:52:25 2020 +0200

    ISIS-2340: share logic of ObjectAdapterAccessHelper/UpdateHelper (5)
---
 .../isis/core/commons/internal/base/_Either.java   |  18 +-
 .../ui/components/object/ObjectFormView.java       | 136 ++++----
 .../common/model/binding/UiComponentFactory.java   |  19 +
 .../model/binding/interaction/ActionBinding.java   |  25 ++
 .../binding/interaction/ActionBindingReadonly.java |  19 +
 .../binding/interaction/ActionBindingUsable.java   |  19 +
 .../binding/interaction/ActionInteractor.java      |  71 ----
 .../binding/interaction/CollectionBinding.java     |  25 ++
 .../interaction/CollectionBindingEditable.java     |  19 +
 .../interaction/CollectionBindingReadonly.java     |  19 +
 .../binding/interaction/CollectionInteractor.java  |  70 ----
 .../binding/interaction/MemberInteractor.java      |  84 -----
 .../model/binding/interaction/ObjectBinding.java   | 387 +++++++++++++++++++++
 .../binding/interaction/ObjectInteractor.java      | 150 --------
 .../model/binding/interaction/PropertyBinding.java |  31 ++
 .../interaction/PropertyBindingEditable.java       |  37 ++
 .../interaction/PropertyBindingReadonly.java       |  27 ++
 .../binding/interaction/PropertyInteractor.java    |  86 -----
 .../resources/DomainObjectResourceServerside.java  |  71 ++--
 .../viewer/resources/DomainResourceHelper.java     |  14 +-
 .../resources/InteractionFailureHandler.java       |   2 +-
 .../resources/ObjectAdapterAccessHelper.java       |  38 +-
 22 files changed, 785 insertions(+), 582 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/internal/base/_Either.java b/core/commons/src/main/java/org/apache/isis/core/commons/internal/base/_Either.java
index 2715b95..74a01b5 100644
--- a/core/commons/src/main/java/org/apache/isis/core/commons/internal/base/_Either.java
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/internal/base/_Either.java
@@ -114,13 +114,25 @@ public final class _Either<L, R> {
     public <X, Y> _Either<X, Y> map(Function<L, X> leftMapper, Function<R, Y> rightMapper){
         return isLeft()
                 ? left(leftMapper.apply(left))
-                        : right(rightMapper.apply(right));
+                : right(rightMapper.apply(right));
     }
 
     public <X, Y> _Either<X, Y> mapNullable(Function<L, X> leftMapper, Function<R, Y> rightMapper){
         return isLeft()
                 ? leftNullable(leftMapper.apply(left))
-                        : rightNullable(rightMapper.apply(right));
+                : rightNullable(rightMapper.apply(right));
+    }
+    
+    public _Either<L, R> leftRemap(Function<L, _Either<L, R>> leftRemapper){
+        return isLeft()
+                ? leftRemapper.apply(left)
+                : this;
+    }
+
+    public _Either<L, R> rightReMap(Function<R, _Either<L, R>> rightRemapper){
+        return isLeft()
+                ? this
+                : rightRemapper.apply(right);
     }
 
     // -- REDUCTION
@@ -132,7 +144,7 @@ public final class _Either<L, R> {
     public <X> X get(Function<L, X> leftMapper, Function<R, X> rightMapper){
         return isLeft()
                 ? leftMapper.apply(left)
-                        : rightMapper.apply(right);
+                : rightMapper.apply(right);
     }
 
     // -- TERMINALS
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/object/ObjectFormView.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/object/ObjectFormView.java
index c771ab9..6333a8f 100644
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/object/ObjectFormView.java
+++ b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/object/ObjectFormView.java
@@ -36,6 +36,7 @@ import com.vaadin.flow.component.tabs.Tab;
 import com.vaadin.flow.component.tabs.Tabs;
 import com.vaadin.flow.component.textfield.TextField;
 
+import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.layout.component.ActionLayoutData;
 import org.apache.isis.applib.layout.component.CollectionLayoutData;
 import org.apache.isis.applib.layout.component.DomainObjectLayoutData;
@@ -50,7 +51,8 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.incubator.viewer.vaadin.ui.components.UiComponentFactoryVaa;
 import org.apache.isis.incubator.viewer.vaadin.ui.components.collection.TableView;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor;
+import org.apache.isis.viewer.common.model.binding.UiComponentFactory;
+import org.apache.isis.viewer.common.model.binding.interaction.ObjectBinding;
 import org.apache.isis.viewer.common.model.gridlayout.UiGridLayout;
 
 import lombok.NonNull;
@@ -59,9 +61,9 @@ import lombok.val;
 public class ObjectFormView extends VerticalLayout {
 
     private static final long serialVersionUID = 1L;
-    
+
     private static final String NULL_LITERAL = "<NULL>";
-    
+
     /**
      * Constructs given domain object's view, with all its visible members and actions.
      * @param managedObject - domain object
@@ -69,63 +71,63 @@ public class ObjectFormView extends VerticalLayout {
     public ObjectFormView(
             @NonNull final UiComponentFactoryVaa uiComponentFactory,
             @NonNull final ManagedObject managedObject) {
-        
-        
-        val objectInteractor = ObjectInteractor.bind(managedObject);
-        
+
+
+        val objectInteractor = ObjectBinding.bind(managedObject);
+
         val uiGridLayout = UiGridLayout.bind(managedObject);
-        
+
         // force new row
         //formLayout.getElement().appendChild(ElementFactory.createBr());
-        
+
         val gridVisistor = new UiGridLayout.Visitor<HasComponents>(this) {
 
             @Override
             protected void onObjectTitle(HasComponents container, DomainObjectLayoutData domainObjectData) {
                 val uiTitle = new H1(objectInteractor.getTitle());
-//                uiTitle.addThemeVariants(
-//                        ButtonVariant.LUMO_LARGE,
-//                        ButtonVariant.LUMO_TERTIARY_INLINE);
+                //                uiTitle.addThemeVariants(
+                //                        ButtonVariant.LUMO_LARGE,
+                //                        ButtonVariant.LUMO_TERTIARY_INLINE);
                 container.add(uiTitle);     
             }
-            
+
             @Override
             protected HasComponents newRow(HasComponents container, BS3Row bs3Row) {
                 val uiRow = new FlexLayout();
                 container.add(uiRow);
                 uiRow.setWidthFull();
                 uiRow.setWrapMode(FlexLayout.WrapMode.WRAP); // allow line breaking
-                
+
                 // instead of a FlexLayout we need to convert to a layout where we can control 
                 // the responsive steps
-//                val steps = _Lists.of(
-//                        new ResponsiveStep("0", 1),
-//                        new ResponsiveStep("50em", 2)
-//                        );
-                
+                //                val steps = _Lists.of(
+                //                        new ResponsiveStep("0", 1),
+                //                        new ResponsiveStep("50em", 2)
+                //                        );
+
                 return uiRow;
             }
 
             @Override
             protected HasComponents newCol(HasComponents container, BS3Col bs3col) {
-                
+
                 val uiCol = new VerticalLayout();
                 container.add(uiCol);
-                
+
                 final int span = bs3col.getSpan();
                 ((FlexLayout)container).setFlexGrow(span, uiCol);
                 val widthEm = String.format("%dem", span * 3); // 1em ~ 16px
                 uiCol.setWidth(null); // clear preset width style
                 uiCol.setMinWidth(widthEm);
-                
+
                 return uiCol;
             }
-            
+
             @Override
             protected HasComponents newActionPanel(HasComponents container) {
                 val uiActionPanel = new FlexLayout();
                 container.add(uiActionPanel);
-                
+
                 uiActionPanel.setWrapMode(FlexLayout.WrapMode.WRAP); // allow line breaking
                 uiActionPanel.setAlignItems(Alignment.BASELINE);
                 return uiActionPanel;
@@ -145,22 +147,22 @@ public class ObjectFormView extends VerticalLayout {
                 container.add(uiTab);
                 return uiTab;
             }
-            
+
             @Override
             protected HasComponents newFieldSet(HasComponents container, FieldSet fieldSetData) {
-                
+
                 container.add(new H2(fieldSetData.getName()));
-                
+
                 val uiFieldSet = new FormLayout();
                 container.add(uiFieldSet);
-                
+
                 uiFieldSet.setResponsiveSteps(
                         new ResponsiveStep("0", 1)); // single column only
-                
+
                 return uiFieldSet;
             }
 
-            
+
             @Override
             protected void onClearfix(HasComponents container, BS3ClearFix clearFixData) {
                 // TODO Auto-generated method stub
@@ -175,16 +177,18 @@ public class ObjectFormView extends VerticalLayout {
                     uiAction.getStyle().set("margin-left", "0.5em");
                     uiAction.addThemeVariants(
                             ButtonVariant.LUMO_SMALL);
-                    
+
                 });
             }
 
             @Override
             protected void onProperty(HasComponents container, PropertyLayoutData propertyData) {
-                objectInteractor.lookupProperty(propertyData.getId())
-                .ifPresent(property->{
-                    val uiPropertyCreateRequest = objectInteractor.newUiComponentCreateRequest(property);
-                    val uiProperty = uiComponentFactory.componentFor(uiPropertyCreateRequest);
+                objectInteractor
+                .getPropertyBinding(propertyData.getId(), Where.OBJECT_FORMS)
+                .left() // if visible
+                .ifPresent(propertyBinding->{
+                    val uiProperty = uiComponentFactory
+                            .componentFor(UiComponentFactory.Request.of(propertyBinding));
                     container.add(uiProperty);                    
                 });
             }
@@ -194,49 +198,49 @@ public class ObjectFormView extends VerticalLayout {
                 objectInteractor.lookupCollection(collectionData.getId())
                 .ifPresent(collection->{
                     container.add(new H3(collection.getName()));
-                    
+
                     val collectionValue = Optional.ofNullable(collection.get(managedObject))
                             .orElse(ManagedObject.of(collection.getSpecification(), null));
                     container.add(createCollectionComponent(collection, collectionValue));
                 });
             }
-            
+
         };
-        
+
         uiGridLayout.visit(gridVisistor);
         setWidthFull();
-        
+
         // -- populate actions
-        
-//        objectInteractor
-//        .streamVisisbleActions()
-//        .forEach(action -> {
-//            
-//            val button = new Button(action.getName());
-//            actionsLayout.add(button);
-//            
-//            Dialog dialog = new Dialog();
-//            dialog.add(new Label("Under Construction ... Close me with the esc-key or an outside click"));
-//
-//            dialog.setWidth("400px");
-//            dialog.setHeight("150px");
-//
-//            button.addClickListener(e->{
-//                dialog.open();
-//            });
-//
-////similar code in menu item ...            
-////            val actionModel = (ActionVaa)menuItemModel.getMenuActionUiModel();
-////            
-////            subMenu.addItem(
-////                    (Component)menuActionModel.getUiComponent(), 
-////                    e->subMenuEventHandler.accept(menuActionModel));
-//            
-//        });
+
+        //        objectInteractor
+        //        .streamVisisbleActions()
+        //        .forEach(action -> {
+        //            
+        //            val button = new Button(action.getName());
+        //            actionsLayout.add(button);
+        //            
+        //            Dialog dialog = new Dialog();
+        //            dialog.add(new Label("Under Construction ... Close me with the esc-key or an outside click"));
+        //
+        //            dialog.setWidth("400px");
+        //            dialog.setHeight("150px");
+        //
+        //            button.addClickListener(e->{
+        //                dialog.open();
+        //            });
+        //
+        ////similar code in menu item ...            
+        ////            val actionModel = (ActionVaa)menuItemModel.getMenuActionUiModel();
+        ////            
+        ////            subMenu.addItem(
+        ////                    (Component)menuActionModel.getUiComponent(), 
+        ////                    e->subMenuEventHandler.accept(menuActionModel));
+        //            
+        //        });
 
 
     }
-    
+
     // -- HELPER
 
     private Component createCollectionComponent(
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/UiComponentFactory.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/UiComponentFactory.java
index 7dffb94..fbff986 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/UiComponentFactory.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/UiComponentFactory.java
@@ -30,6 +30,7 @@ import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.isis.viewer.common.model.binding.interaction.PropertyBinding;
 
 import lombok.NonNull;
 import lombok.Value;
@@ -48,6 +49,22 @@ public interface UiComponentFactory<T> {
         @NonNull private final ObjectFeature objectFeature;
         @NonNull private final Function<ManagedObject, String> toDomainPropagator;
         
+        public static Request of(PropertyBinding propertyBinding) {
+            val property = propertyBinding.getProperty();
+            //TODO there is also the aspect of editable or not
+            val propertyValue = propertyBinding.getPropertyValue();
+            
+            return of(propertyValue, property, proposedNewValue -> {
+
+                val iResponse = propertyBinding.modifyProperty(proposedNewValue);
+                if (iResponse.isFailure()) {
+                    return iResponse.getFailureMessage(); // validation result if any
+                }
+                return null;
+
+            });
+        }
+        
         // -- SHORTCUTS
         
         /**
@@ -129,6 +146,8 @@ public interface UiComponentFactory<T> {
         public boolean isReadOnly() {
             return objectFeature.getFacet(DisabledFacet.class)!=null;
         }
+
+
         
     }
     
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBinding.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBinding.java
new file mode 100644
index 0000000..45aee47
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBinding.java
@@ -0,0 +1,25 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+
+public interface ActionBinding {
+
+    ManagedObject getManagedObject();
+    ObjectAction getAction();
+
+//    InteractionResponse modifyProperty(ManagedObject newProperyValue);
+//    
+//    default InteractionResponse modifyProperty(
+//            @NonNull final Function<OneToOneAssociation, ManagedObject> newProperyValueProvider) {
+//        return modifyProperty(newProperyValueProvider.apply(getProperty()));
+//    }
+//    
+//    default ManagedObject getPropertyValue() {
+//        val property = getProperty();
+//        
+//        return Optional.ofNullable(property.get(getManagedObject()))
+//        .orElse(ManagedObject.of(property.getSpecification(), null));
+//    }
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingReadonly.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingReadonly.java
new file mode 100644
index 0000000..09f5b55
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingReadonly.java
@@ -0,0 +1,19 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName = "of")
+public class ActionBindingReadonly implements ActionBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final ObjectAction action;
+    
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingUsable.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingUsable.java
new file mode 100644
index 0000000..2bdc815
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionBindingUsable.java
@@ -0,0 +1,19 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName = "of")
+public class ActionBindingUsable implements ActionBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final ObjectAction action;
+    
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionInteractor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionInteractor.java
deleted file mode 100644
index 3a825cc..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ActionInteractor.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.common.model.binding.interaction;
-
-import java.util.function.BiConsumer;
-
-import javax.annotation.Nullable;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.internal.base._Either;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor.AccessIntent;
-
-import lombok.val;
-
-public class ActionInteractor extends MemberInteractor {
-
-    private final String actionId;
-    private final Where where;
-    private final AccessIntent accessIntent;
-    
-    public ActionInteractor(
-            final ObjectInteractor objectInteractor,
-            final String actionId,
-            final Where where,
-            final AccessIntent accessIntent) {
-        super(objectInteractor);
-        this.actionId = actionId;
-        this.where = where;
-        this.accessIntent = accessIntent;
-    }
-
-    public _Either<ObjectAction, InteractionResponse> getActionThatIsVisibleForIntent() {
-
-        val managedObject = objectInteractor.getManagedObject();
-        
-        val spec = managedObject.getSpecification();
-        val action = spec.getObjectAction(actionId).orElse(null);
-        
-        if(action==null) {
-            return super.notFound(MemberType.ACTION, actionId, Veto.NOT_FOUND);
-        }
-        
-        return super.memberThatIsVisibleForIntent(
-                MemberType.ACTION,
-                action, where, accessIntent);
-    }
-    
-    public ActionInteractor onFailure(@Nullable final BiConsumer<InteractionResponse, String> onFailure) {
-        super.onFailure = onFailure;
-        return this;
-    }
-
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBinding.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBinding.java
new file mode 100644
index 0000000..92de7cd
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBinding.java
@@ -0,0 +1,25 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+public interface CollectionBinding {
+
+    ManagedObject getManagedObject();
+    OneToManyAssociation getCollection();
+
+//    InteractionResponse modifyProperty(ManagedObject newProperyValue);
+//    
+//    default InteractionResponse modifyProperty(
+//            @NonNull final Function<OneToOneAssociation, ManagedObject> newProperyValueProvider) {
+//        return modifyProperty(newProperyValueProvider.apply(getProperty()));
+//    }
+//    
+//    default ManagedObject getPropertyValue() {
+//        val property = getProperty();
+//        
+//        return Optional.ofNullable(property.get(getManagedObject()))
+//        .orElse(ManagedObject.of(property.getSpecification(), null));
+//    }
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingEditable.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingEditable.java
new file mode 100644
index 0000000..877e654
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingEditable.java
@@ -0,0 +1,19 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName = "of")
+public class CollectionBindingEditable implements CollectionBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final OneToManyAssociation collection;
+    
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingReadonly.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingReadonly.java
new file mode 100644
index 0000000..d2a4b03
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionBindingReadonly.java
@@ -0,0 +1,19 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName = "of")
+public class CollectionBindingReadonly implements CollectionBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final OneToManyAssociation collection;
+    
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionInteractor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionInteractor.java
deleted file mode 100644
index 488a129..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/CollectionInteractor.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.common.model.binding.interaction;
-
-import java.util.function.BiConsumer;
-
-import javax.annotation.Nullable;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.internal.base._Either;
-import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
-import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor.AccessIntent;
-
-import lombok.val;
-
-public class CollectionInteractor extends MemberInteractor {
-
-    private final String collectionId;
-    private final Where where;
-    private final AccessIntent accessIntent;
-    
-    public CollectionInteractor(
-            final ObjectInteractor objectInteractor,
-            final String collectionId,
-            final Where where,
-            final AccessIntent accessIntent) {
-        super(objectInteractor);
-        this.collectionId = collectionId;
-        this.where = where;
-        this.accessIntent = accessIntent;
-    }
-
-    public _Either<OneToManyAssociation, InteractionResponse> getCollectionThatIsVisibleForIntent() {
-
-        val managedObject = objectInteractor.getManagedObject();
-        
-        val spec = managedObject.getSpecification();
-        val collection = spec.getAssociation(collectionId).orElse(null);
-        if(collection==null || !collection.isOneToOneAssociation()) {
-            return super.notFound(MemberType.COLLECTION, collectionId, Veto.NOT_FOUND);
-        }
-        
-        return super.memberThatIsVisibleForIntent(
-                MemberType.COLLECTION,
-                (OneToManyAssociation) collection, where, accessIntent);
-    }
-    
-    public CollectionInteractor onFailure(@Nullable final BiConsumer<InteractionResponse, String> onFailure) {
-        super.onFailure = onFailure;
-        return this;
-    }
-
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/MemberInteractor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/MemberInteractor.java
deleted file mode 100644
index 3177440..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/MemberInteractor.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.common.model.binding.interaction;
-
-import java.util.function.BiConsumer;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.internal.base._Either;
-import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
-import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor.AccessIntent;
-
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-
-@RequiredArgsConstructor
-class MemberInteractor {
-    
-    // only used to create failure messages
-    static enum MemberType {
-        PROPERTY,
-        COLLECTION,
-        ACTION
-    }
-    
-    protected final ObjectInteractor objectInteractor;
-    protected BiConsumer<InteractionResponse, String> onFailure;
-
-    protected <T extends ObjectMember> 
-    _Either<T, InteractionResponse> memberThatIsVisibleForIntent(
-            final MemberType memberType,
-            final T objectMember, 
-            final Where where, 
-            final AccessIntent intent) {
-
-        val managedObject = objectInteractor.getManagedObject();
-        val visibilityConsent =
-                objectMember.isVisible(
-                        managedObject, InteractionInitiatedBy.USER, where);
-        if (visibilityConsent.isVetoed()) {
-            val memberId = objectMember.getId();
-            return notFound(memberType, memberId, Veto.HIDDEN);
-        }
-        if (intent.isMutate()) {
-            val usabilityConsent = 
-                objectMember.isUsable(
-                    managedObject, InteractionInitiatedBy.USER, where);
-            if (usabilityConsent.isVetoed()) {
-                return _Either.right(InteractionResponse.failed(
-                        Veto.FORBIDDEN,
-                        usabilityConsent.getReason()));
-            }
-        }
-        return _Either.left(objectMember);
-    }
-    
-    protected <T extends ObjectMember> 
-    _Either<T, InteractionResponse> notFound(MemberType memberType, String memberId, Veto veto) {
-        return _Either.right(InteractionResponse.failed(
-                veto,
-                String.format("%s '%s' either does not exist, is disabled or is not visible", 
-                        memberId, 
-                        memberType.name().toLowerCase())));
-    }
-    
-    
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectBinding.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectBinding.java
new file mode 100644
index 0000000..8b1a71d
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectBinding.java
@@ -0,0 +1,387 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.commons.internal.base._Either;
+import org.apache.isis.core.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@RequiredArgsConstructor(staticName = "bind")
+public class ObjectBinding {
+    
+    // only used to create failure messages
+    @RequiredArgsConstructor
+    private static enum MemberType {
+        PROPERTY(OneToOneAssociation.class, (spec, propertyId)->
+        spec.getAssociation(propertyId)
+        .map(property->property.isOneToOneAssociation()?property:null)),
+        
+        COLLECTION(OneToManyAssociation.class, (spec, collectionId)->
+        spec.getAssociation(collectionId)
+        .map(collection->collection.isOneToManyAssociation()?collection:null)),
+        
+        ACTION(ObjectAction.class, (spec, actionId)->
+        spec.getObjectAction(actionId));
+        
+        @Getter private final Class<? extends ObjectMember> memberType;
+        @Getter private final BiFunction<
+                ObjectSpecification, String, 
+                Optional<? extends ObjectMember>
+            > memberProvider;
+        
+    }
+    
+    public static enum AccessIntent {
+        ACCESS, MUTATE;
+
+        public boolean isMutate() {
+            return this == MUTATE;
+        }
+    }
+
+    @Getter
+    private final ManagedObject managedObject;
+    
+    public String getTitle() {
+        return managedObject.getSpecification().getTitle(null, managedObject);
+    }
+
+    @Deprecated
+    public Stream<OneToOneAssociation> streamVisisbleProperties() {
+        return managedObject.getSpecification()
+        .streamAssociations(Contributed.INCLUDED)
+        .filter(objMember->objMember.getFeatureType().isProperty())
+        //TODO filter visibility
+        .map(OneToOneAssociation.class::cast);
+    }
+    
+    @Deprecated
+    public Stream<OneToManyAssociation> streamVisisbleCollections() {
+        return managedObject.getSpecification()
+        .streamAssociations(Contributed.INCLUDED)
+        .filter(objMember->objMember.getFeatureType().isCollection())
+        //TODO filter visibility
+        .map(OneToManyAssociation.class::cast);
+    }
+    
+    @Deprecated
+    public Stream<ObjectAction> streamVisisbleActions() {
+        return managedObject.getSpecification()
+        .streamObjectActions(Contributed.INCLUDED)
+        .filter(objMember->objMember.getFeatureType().isAction())
+        //TODO filter visibility
+        .map(ObjectAction.class::cast);
+    }
+    
+    public Optional<ObjectAction> lookupAction(String actionId) {
+        return streamVisisbleActions()
+                .filter(action->Objects.equals(actionId, action.getId()))
+                .findFirst();
+    }
+    
+    public Optional<OneToOneAssociation> lookupVisibleProperty(String propertyId) {
+        return streamVisisbleProperties()
+                .filter(property->Objects.equals(propertyId, property.getId()))
+                .findFirst();
+    }
+
+    public Optional<OneToManyAssociation> lookupCollection(String collectionId) {
+        return streamVisisbleCollections()
+                .filter(collection->Objects.equals(collectionId, collection.getId()))
+                .findFirst();
+    }
+
+    /**
+     * @param actionId
+     * @param where
+     * @return either an editable or readonly ActionBinding, based on visibility and usability
+     */
+    public _Either<ActionBinding, InteractionResponse> getActionBinding(String actionId, Where where) {
+        return getActionThatIsVisible(actionId, where)
+        .map(
+                action->
+                    checkUsability(action, where).isSuccess()
+                       ? ActionBindingUsable.of(managedObject, action)
+                       : ActionBindingReadonly.of(managedObject, action), 
+                UnaryOperator.identity());
+    }
+    
+    /**
+     * 
+     * @param actionId
+     * @param where
+     * @param accessIntent
+     * @return either an editable or readonly PropertyBinding, based on visibility and accessIntent
+     */
+    public _Either<ActionBinding, InteractionResponse> getActionBinding(
+            final String actionId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        
+        return getActionThatIsVisibleForIntent(actionId, where, accessIntent)
+        .map(
+                action->{
+                    switch(accessIntent) {
+                    case ACCESS:
+                        return ActionBindingReadonly.of(managedObject, action);
+                    case MUTATE:
+                        return ActionBindingUsable.of(managedObject, action);
+                    default:
+                        throw _Exceptions.unmatchedCase(accessIntent);
+                    }
+                },
+                UnaryOperator.identity());
+
+    }
+    
+    /**
+     * @param propertyId
+     * @param where
+     * @return either an editable or readonly PropertyBinding, based on visibility and usability
+     */
+    public _Either<PropertyBinding, InteractionResponse> getPropertyBinding(String propertyId, Where where) {
+        return getPropertyThatIsVisible(propertyId, where)
+        .map(
+                property->
+                    checkUsability(property, where).isSuccess()
+                       ? PropertyBindingEditable.of(managedObject, property)
+                       : PropertyBindingReadonly.of(managedObject, property), 
+                UnaryOperator.identity());
+    }
+    
+    /**
+     * 
+     * @param propertyId
+     * @param where
+     * @param accessIntent
+     * @return either an editable or readonly PropertyBinding, based on visibility and accessIntent
+     */
+    public _Either<PropertyBinding, InteractionResponse> getPropertyBinding(
+            final String propertyId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        
+        return getPropertyThatIsVisibleForIntent(propertyId, where, accessIntent)
+        .map(
+                property->{
+                    switch(accessIntent) {
+                    case ACCESS:
+                        return PropertyBindingReadonly.of(managedObject, property);
+                    case MUTATE:
+                        return PropertyBindingEditable.of(managedObject, property);
+                    default:
+                        throw _Exceptions.unmatchedCase(accessIntent);
+                    }
+                },
+                UnaryOperator.identity());
+
+    }
+    
+    /**
+     * @param collectionId
+     * @param where
+     * @return either an editable or readonly CollectionBinding, based on visibility and usability
+     */
+    public _Either<CollectionBinding, InteractionResponse> getCollectionBinding(String collectionId, Where where) {
+        return getCollectionThatIsVisible(collectionId, where)
+        .map(
+                coll->
+                    checkUsability(coll, where).isSuccess()
+                       ? CollectionBindingEditable.of(managedObject, coll)
+                       : CollectionBindingReadonly.of(managedObject, coll), 
+                UnaryOperator.identity());
+    }
+    
+    /**
+     * 
+     * @param collectionId
+     * @param where
+     * @param accessIntent
+     * @return either an editable or readonly CollectionBinding, based on visibility and accessIntent
+     */
+    public _Either<CollectionBinding, InteractionResponse> getCollectionBinding(
+            final String collectionId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        
+        return getCollectionThatIsVisibleForIntent(collectionId, where, accessIntent)
+        .map(
+                coll->{
+                    switch(accessIntent) {
+                    case ACCESS:
+                        return CollectionBindingReadonly.of(managedObject, coll);
+                    case MUTATE:
+                        return CollectionBindingEditable.of(managedObject, coll);
+                    default:
+                        throw _Exceptions.unmatchedCase(accessIntent);
+                    }
+                },
+                UnaryOperator.identity());
+
+    }
+    
+    // -- HELPER - TOP LEVEL
+
+    private _Either<ObjectAction, InteractionResponse> 
+    getActionThatIsVisible(String actionId, Where where) {
+        val chain = this.<ObjectAction>startChain(MemberType.ACTION, actionId);
+        return checkVisibility(chain, MemberType.ACTION, where);
+    }
+    
+    private _Either<OneToOneAssociation, InteractionResponse> 
+    getPropertyThatIsVisible(String propertyId, Where where) {
+        val chain = this.<OneToOneAssociation>startChain(MemberType.PROPERTY, propertyId);
+        return checkVisibility(chain, MemberType.PROPERTY, where);
+    }
+    
+    private _Either<OneToManyAssociation, InteractionResponse> 
+    getCollectionThatIsVisible(String collectionId, Where where) {
+        val chain = this.<OneToManyAssociation>startChain(MemberType.COLLECTION, collectionId);
+        return checkVisibility(chain, MemberType.COLLECTION, where);
+    }
+    
+    private _Either<ObjectAction, InteractionResponse> getActionThatIsVisibleForIntent(
+            final String actionId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        return checkUsability(getActionThatIsVisible(actionId, where), where, accessIntent);
+    }
+    
+    private _Either<OneToOneAssociation, InteractionResponse> getPropertyThatIsVisibleForIntent(
+            final String propertyId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        return checkUsability(getPropertyThatIsVisible(propertyId, where), where, accessIntent);
+    }
+    
+    private _Either<OneToManyAssociation, InteractionResponse> getCollectionThatIsVisibleForIntent(
+            final String propertyId, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        return checkUsability(getCollectionThatIsVisible(propertyId, where), where, accessIntent);
+    }
+    
+    // HELPER - MID LEVEL
+    
+    private <T extends ObjectMember> _Either<T, InteractionResponse> startChain(
+            final MemberType memberType,
+            final String memberId) {
+        
+        val spec = managedObject.getSpecification();
+        val member = memberType.getMemberProvider().apply(spec, memberId);
+        
+        if(!member.isPresent()) {
+            return _Either.right(InteractionResponse.failed(
+                    Veto.NOT_FOUND,
+                    notFound(memberType, memberId)));
+        }
+        return _Either.<T, InteractionResponse>left((T)member.get());
+    }
+    
+    private <T extends ObjectMember> _Either<T, InteractionResponse> checkVisibility(
+            final _Either<T, InteractionResponse> chain,
+            final MemberType memberType,
+            final Where where) {
+        
+        return chain.leftRemap(objectMember->{
+            val visibility = checkVisibility(memberType, objectMember, where);
+            if(visibility.isFailure()) {
+                return _Either.right(visibility);
+            }
+            return _Either.left(objectMember);
+            
+        });
+    }
+    
+    private <T extends ObjectMember> _Either<T, InteractionResponse> checkUsability(
+            final _Either<T, InteractionResponse> chain, 
+            final Where where, 
+            final AccessIntent accessIntent) {
+        
+        return chain.leftRemap(objectMember->{
+            if(accessIntent.isMutate()) {
+                val usability = checkUsability(objectMember, where);
+                if(usability.isFailure()) {
+                    return _Either.right(usability);
+                }
+            }
+            return _Either.left(objectMember);
+            
+        });
+    }
+
+    // HELPER - LOWEST LEVEL
+    
+    private <T extends ObjectMember> InteractionResponse checkVisibility(
+            final MemberType memberType,
+            final T objectMember, 
+            final Where where) {
+
+        val visibilityConsent =
+                objectMember.isVisible(
+                        managedObject, InteractionInitiatedBy.USER, where);
+        return visibilityConsent.isVetoed()
+                ? InteractionResponse.failed(
+                    Veto.HIDDEN,
+                    notFound(memberType, objectMember.getId()))
+                : InteractionResponse.success();
+    }
+    
+    private <T extends ObjectMember> InteractionResponse checkUsability(
+            final T objectMember, 
+            final Where where) {
+        
+        val usabilityConsent = 
+                objectMember.isUsable(
+                    managedObject, InteractionInitiatedBy.USER, where);
+        return usabilityConsent.isVetoed()
+                ? InteractionResponse.failed(
+                    Veto.FORBIDDEN,
+                    usabilityConsent.getReason())
+                : InteractionResponse.success();
+    }
+   
+    private <T extends ObjectMember> String notFound(
+            MemberType memberType, 
+            String memberId) {
+        return String.format("%s '%s' either does not exist, is disabled or is not visible", 
+                        memberId, 
+                        memberType.name().toLowerCase());
+    }
+    
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectInteractor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectInteractor.java
deleted file mode 100644
index 60b5e89..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/ObjectInteractor.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.common.model.binding.interaction;
-
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.isis.core.metamodel.spec.ManagedObject;
-import org.apache.isis.core.metamodel.spec.feature.Contributed;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
-import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.viewer.common.model.binding.UiComponentFactory;
-import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-
-@RequiredArgsConstructor(staticName = "bind")
-public class ObjectInteractor {
-    
-    public static enum AccessIntent {
-        ACCESS, MUTATE;
-
-        public boolean isMutate() {
-            return this == MUTATE;
-        }
-    }
-
-    @Getter
-    private final ManagedObject managedObject;
-    
-    public String getTitle() {
-        return managedObject.getSpecification().getTitle(null, managedObject);
-    }
-
-    public Stream<OneToOneAssociation> streamVisisbleProperties() {
-        return managedObject.getSpecification()
-        .streamAssociations(Contributed.INCLUDED)
-        .filter(objMember->objMember.getFeatureType().isProperty())
-        //TODO filter visibility
-        .map(OneToOneAssociation.class::cast);
-    }
-    
-    public Stream<OneToManyAssociation> streamVisisbleCollections() {
-        return managedObject.getSpecification()
-        .streamAssociations(Contributed.INCLUDED)
-        .filter(objMember->objMember.getFeatureType().isCollection())
-        //TODO filter visibility
-        .map(OneToManyAssociation.class::cast);
-    }
-    
-    public Stream<ObjectAction> streamVisisbleActions() {
-        return managedObject.getSpecification()
-        .streamObjectActions(Contributed.INCLUDED)
-        .filter(objMember->objMember.getFeatureType().isAction())
-        //TODO filter visibility
-        .map(ObjectAction.class::cast);
-    }
-    
-    public InteractionResponse modifyProperty(
-            final OneToOneAssociation property, 
-            final ManagedObject proposedNewValue) {
-        
-        val consent = property.isAssociationValid(managedObject, proposedNewValue, InteractionInitiatedBy.USER);
-        if (consent.isVetoed()) {
-            return InteractionResponse.failed(Veto.UNAUTHORIZED, consent.getReason());
-        }
-        
-        property.set(managedObject, proposedNewValue, InteractionInitiatedBy.USER);
-        
-        return InteractionResponse.success();
-    }
-
-    public UiComponentFactory.Request newUiComponentCreateRequest(
-            final OneToOneAssociation property) {
-        
-        val propertyValue = Optional.ofNullable(property.get(managedObject))
-                .orElse(ManagedObject.of(property.getSpecification(), null));
-        
-        return UiComponentFactory.Request
-        .of(propertyValue, property, proposedNewValue -> {
-            
-            val iResponse = modifyProperty(property, proposedNewValue);
-            if (iResponse.isFailure()) {
-                return iResponse.getFailureMessage(); // validation result if any
-            }
-            return null;
-            
-        });
-        
-    }
-
-    public Optional<ObjectAction> lookupAction(String id) {
-        return streamVisisbleActions()
-                .filter(action->Objects.equals(id, action.getId()))
-                .findFirst();
-    }
-    
-    public Optional<OneToOneAssociation> lookupProperty(String id) {
-        return streamVisisbleProperties()
-                .filter(property->Objects.equals(id, property.getId()))
-                .findFirst();
-    }
-
-    public Optional<OneToManyAssociation> lookupCollection(String id) {
-        return streamVisisbleCollections()
-                .filter(collection->Objects.equals(id, collection.getId()))
-                .findFirst();
-    }
-
-    public ActionInteractor getActionInteractor(
-            String actionId, Where where, AccessIntent accessIntent) {
-        return new ActionInteractor(this, actionId, where, accessIntent);
-    }
-    
-    public PropertyInteractor getPropertyInteractor(
-            String propertyId, Where where, AccessIntent accessIntent) {
-        return new PropertyInteractor(this, propertyId, where, accessIntent);
-    }
-    
-    public CollectionInteractor getCollectionInteractor(
-            String collectionId, Where where, AccessIntent accessIntent) {
-        return new CollectionInteractor(this, collectionId, where, accessIntent);
-    }
-
-    
-    
-    
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBinding.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBinding.java
new file mode 100644
index 0000000..690e8bb
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBinding.java
@@ -0,0 +1,31 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+
+import lombok.NonNull;
+import lombok.val;
+
+public interface PropertyBinding {
+
+    ManagedObject getManagedObject();
+    OneToOneAssociation getProperty();
+
+    InteractionResponse modifyProperty(ManagedObject newProperyValue);
+    
+    default InteractionResponse modifyProperty(
+            @NonNull final Function<OneToOneAssociation, ManagedObject> newProperyValueProvider) {
+        return modifyProperty(newProperyValueProvider.apply(getProperty()));
+    }
+    
+    default ManagedObject getPropertyValue() {
+        val property = getProperty();
+        
+        return Optional.ofNullable(property.get(getManagedObject()))
+        .orElse(ManagedObject.of(property.getSpecification(), null));
+    }
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingEditable.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingEditable.java
new file mode 100644
index 0000000..d7f54e1
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingEditable.java
@@ -0,0 +1,37 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import javax.annotation.Nullable;
+
+import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@RequiredArgsConstructor(staticName = "of")
+public class PropertyBindingEditable implements PropertyBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final OneToOneAssociation property;
+
+
+    @Override
+    public InteractionResponse modifyProperty(@Nullable ManagedObject proposedNewValue) {
+            
+        val consent = property.isAssociationValid(getManagedObject(), proposedNewValue, InteractionInitiatedBy.USER);
+        if (consent.isVetoed()) {
+            return InteractionResponse.failed(Veto.UNAUTHORIZED, consent.getReason());
+        }
+        
+        property.set(managedObject, proposedNewValue, InteractionInitiatedBy.USER);
+        
+        return InteractionResponse.success();
+    }
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingReadonly.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingReadonly.java
new file mode 100644
index 0000000..3c8ed80
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyBindingReadonly.java
@@ -0,0 +1,27 @@
+package org.apache.isis.viewer.common.model.binding.interaction;
+
+import javax.annotation.Nullable;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName = "of")
+public class PropertyBindingReadonly implements PropertyBinding {
+
+    @Getter(onMethod = @__(@Override))
+    private final ManagedObject managedObject;
+    
+    @Getter(onMethod = @__(@Override))
+    private final OneToOneAssociation property;
+
+    @Override
+    public InteractionResponse modifyProperty(@Nullable ManagedObject newProperyValue) {
+        // if this code is ever reached we have wired up something wrong internally
+        return InteractionResponse.failed(Veto.FORBIDDEN);   
+    }
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyInteractor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyInteractor.java
deleted file mode 100644
index 79627bd..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/binding/interaction/PropertyInteractor.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.common.model.binding.interaction;
-
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-
-import javax.annotation.Nullable;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.internal.base._Either;
-import org.apache.isis.core.metamodel.spec.ManagedObject;
-import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.viewer.common.model.binding.interaction.InteractionResponse.Veto;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor.AccessIntent;
-
-import lombok.val;
-
-public class PropertyInteractor extends MemberInteractor {
-
-    private final String propertyId;
-    private final Where where;
-    private final AccessIntent accessIntent;
-    
-    public PropertyInteractor(
-            final ObjectInteractor objectInteractor,
-            final String propertyId,
-            final Where where,
-            final AccessIntent accessIntent) {
-        super(objectInteractor);
-        this.propertyId = propertyId;
-        this.where = where;
-        this.accessIntent = accessIntent;
-    }
-
-    public _Either<OneToOneAssociation, InteractionResponse> getPropertyThatIsVisibleForIntent() {
-
-        val managedObject = objectInteractor.getManagedObject();
-        
-        val spec = managedObject.getSpecification();
-        val property = spec.getAssociation(propertyId).orElse(null);
-        if(property==null || !property.isOneToOneAssociation()) {
-            return super.notFound(MemberType.PROPERTY, propertyId, Veto.NOT_FOUND);
-        }
-        
-        return super.memberThatIsVisibleForIntent(
-                MemberType.PROPERTY, (OneToOneAssociation)property, where, accessIntent);
-    }
-
-    public PropertyInteractor onFailure(@Nullable final BiConsumer<InteractionResponse, String> onFailure) {
-        super.onFailure = onFailure;
-        return this;
-    }
-
-    public InteractionResponse modifyProperty(Function<OneToOneAssociation, ManagedObject> newProperyValueProvider) {
-        
-        val check = getPropertyThatIsVisibleForIntent();
-        if(check.isRight()) {
-            return check.rightIfAny();
-        }
-
-        val property = check.leftIfAny();
-        val proposedNewValue = newProperyValueProvider.apply(property);
-        
-        return objectInteractor.modifyProperty(property, proposedNewValue);
-    }
-
-    
-
-}
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
index cc2748c..3179d0f 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
@@ -47,6 +47,7 @@ import org.apache.isis.applib.layout.grid.Grid;
 import org.apache.isis.applib.layout.links.Link;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.core.commons.internal.base._Bytes;
+import org.apache.isis.core.commons.internal.base._Either;
 import org.apache.isis.core.commons.internal.base._Strings;
 import org.apache.isis.core.commons.internal.codec._UrlDecoderUtil;
 import org.apache.isis.core.commons.internal.resources._Resources;
@@ -60,7 +61,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.runtime.iactn.IsisInteractionTracker;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor;
+import org.apache.isis.viewer.common.model.binding.interaction.ObjectBinding;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
@@ -450,21 +451,31 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         setCommandExecutor(Command.Executor.USER);
 
         val objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        val propertyInteractor = ObjectInteractor.bind(objectAdapter)
-                .getPropertyInteractor(
-                    propertyId,
-                    resourceContext.getWhere(),
-                    ObjectInteractor.AccessIntent.MUTATE);
         
-        propertyInteractor
-        .onFailure(InteractionFailureHandler::onFailure)
-        .modifyProperty(property->{
+        ObjectBinding.bind(objectAdapter)
+        .getPropertyBinding(
+            propertyId,
+            resourceContext.getWhere(),
+            ObjectBinding.AccessIntent.MUTATE)
+        .leftRemap(propertyBinding->{
             
-            val proposedNewValue = new JsonParserHelper(resourceContext, property.getSpecification())
-                    .parseAsMapWithSingleValue(Util.asStringUtf8(body));
+            val iResponse = propertyBinding.modifyProperty(property->{
+                
+                val proposedNewValue = new JsonParserHelper(resourceContext, property.getSpecification())
+                        .parseAsMapWithSingleValue(Util.asStringUtf8(body));
+                
+                return proposedNewValue;
+            });
             
-            return proposedNewValue;
-        });
+            if(iResponse.isFailure()) {
+                _Either.right(iResponse);
+            }
+            
+            return _Either.left(propertyBinding);
+        })
+        .right()
+        .ifPresent(InteractionFailureHandler::onFailure);
+        ;
 
         val domainResourceHelper = DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter);
         return domainResourceHelper.propertyDetails(
@@ -489,15 +500,27 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         setCommandExecutor(Command.Executor.USER);
         
         val objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        val propertyInteractor = ObjectInteractor.bind(objectAdapter)
-                .getPropertyInteractor(
-                    propertyId,
-                    resourceContext.getWhere(),
-                    ObjectInteractor.AccessIntent.MUTATE);
         
-        propertyInteractor
-        .onFailure(InteractionFailureHandler::onFailure)
-        .modifyProperty(property->null);
+        ObjectBinding.bind(objectAdapter)
+        .getPropertyBinding(
+            propertyId,
+            resourceContext.getWhere(),
+            ObjectBinding.AccessIntent.MUTATE)
+        .leftRemap(propertyBinding->{
+            
+            val iResponse = propertyBinding.modifyProperty(property->{
+                return null;
+            });
+            
+            if(iResponse.isFailure()) {
+                _Either.right(iResponse);
+            }
+            
+            return _Either.left(propertyBinding);
+        })
+        .right()
+        .ifPresent(InteractionFailureHandler::onFailure);
+        ;
 
         val domainResourceHelper = DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter);
         return domainResourceHelper.propertyDetails(
@@ -553,7 +576,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
         final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
-                collectionId, ObjectInteractor.AccessIntent.MUTATE);
+                collectionId, ObjectBinding.AccessIntent.MUTATE);
 
         if (!collection.getCollectionSemantics().isAnySet()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Collection '%s' does not have set semantics", collectionId);
@@ -592,7 +615,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
         final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
-                collectionId, ObjectInteractor.AccessIntent.MUTATE);
+                collectionId, ObjectBinding.AccessIntent.MUTATE);
 
         if (!collection.getCollectionSemantics().isListOrArray()) {
             throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Collection '%s' does not have list or array semantics", collectionId);
@@ -631,7 +654,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         final ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
         final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(
-                collectionId, ObjectInteractor.AccessIntent.MUTATE);
+                collectionId, ObjectBinding.AccessIntent.MUTATE);
 
         final ObjectSpecification collectionSpec = collection.getSpecification();
         final ManagedObject argAdapter = new JsonParserHelper(resourceContext, collectionSpec)
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java
index 279e5ab..56dee24 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java
@@ -27,7 +27,7 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor;
+import org.apache.isis.viewer.common.model.binding.interaction.ObjectBinding;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.RestfulResponse;
 import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext;
@@ -109,7 +109,7 @@ class DomainResourceHelper {
 
         ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final OneToOneAssociation property = accessHelper.getPropertyThatIsVisibleForIntent(propertyId, ObjectInteractor.AccessIntent.ACCESS);
+        final OneToOneAssociation property = accessHelper.getPropertyThatIsVisibleForIntent(propertyId, ObjectBinding.AccessIntent.ACCESS);
 
         transactionService.flushTransaction();
         return representationService.propertyDetails(resourceContext, new ObjectAndProperty2(objectAdapter, property, memberMode), memberMode);
@@ -127,7 +127,7 @@ class DomainResourceHelper {
 
         ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(collectionId, ObjectInteractor.AccessIntent.ACCESS);
+        final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(collectionId, ObjectBinding.AccessIntent.ACCESS);
 
         transactionService.flushTransaction();
         return representationService.collectionDetails(resourceContext, new ObjectAndCollection2(objectAdapter, collection, memberMode), memberMode);
@@ -143,7 +143,7 @@ class DomainResourceHelper {
 
         ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectInteractor.AccessIntent.ACCESS);
+        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectBinding.AccessIntent.ACCESS);
 
         transactionService.flushTransaction();
         return representationService.actionPrompt(resourceContext, new ObjectAndAction(objectAdapter, action));
@@ -163,7 +163,7 @@ class DomainResourceHelper {
 
         final ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectInteractor.AccessIntent.MUTATE);
+        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectBinding.AccessIntent.MUTATE);
 
         final SemanticsOf actionSemantics = action.getSemantics();
         if (! actionSemantics.isSafeInNature()) {
@@ -187,7 +187,7 @@ class DomainResourceHelper {
 
         final ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectInteractor.AccessIntent.MUTATE);
+        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectBinding.AccessIntent.MUTATE);
 
         final SemanticsOf actionSemantics = action.getSemantics();
         if (!actionSemantics.isIdempotentInNature()) {
@@ -205,7 +205,7 @@ class DomainResourceHelper {
 
         ObjectAdapterAccessHelper accessHelper = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter);
 
-        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectInteractor.AccessIntent.MUTATE);
+        final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectBinding.AccessIntent.MUTATE);
 
         return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.EXCLUDED);
     }
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java
index 1fd0840..4251651 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java
@@ -24,7 +24,7 @@ import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplication
 
 public class InteractionFailureHandler {
 
-    public static void onFailure(final InteractionResponse failure, final String memberId) {
+    public static void onFailure(final InteractionResponse failure) {
         
         if(failure==null || failure.isSuccess()) {
             return;
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
index c0fdeb6..cdfae79 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectAdapterAccessHelper.java
@@ -23,8 +23,8 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor;
-import org.apache.isis.viewer.common.model.binding.interaction.ObjectInteractor.AccessIntent;
+import org.apache.isis.viewer.common.model.binding.interaction.ObjectBinding;
+import org.apache.isis.viewer.common.model.binding.interaction.ObjectBinding.AccessIntent;
 import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext;
 
 import lombok.RequiredArgsConstructor;
@@ -41,47 +41,45 @@ public class ObjectAdapterAccessHelper {
             final IResourceContext resourceContext,
             final ManagedObject managedObject) {
         return new ObjectAdapterAccessHelper(
-                ObjectInteractor.bind(managedObject),
+                ObjectBinding.bind(managedObject),
                 resourceContext.getWhere());
     }
 
-    private final ObjectInteractor objectInteractor;
+    private final ObjectBinding objectInteractor;
     private final Where where;
 
     public OneToOneAssociation getPropertyThatIsVisibleForIntent(
             final String propertyId, final AccessIntent intent) {
 
-        val propertyInteractor = objectInteractor.getPropertyInteractor(propertyId, where, intent);
+        val propertyBindingOrFailure = objectInteractor.getPropertyBinding(propertyId, where, intent);
 
-        val check = propertyInteractor.getPropertyThatIsVisibleForIntent();
-        check.right()
-        .ifPresent(failure->InteractionFailureHandler.onFailure(failure, propertyId));
+        propertyBindingOrFailure.right()
+        .ifPresent(InteractionFailureHandler::onFailure);
 
-        return check.leftIfAny();
+        return propertyBindingOrFailure.leftIfAny().getProperty();
     }
 
     public OneToManyAssociation getCollectionThatIsVisibleForIntent(
             final String collectionId, final AccessIntent intent) {
+        
+        val collectionBindingOrFailure = objectInteractor.getCollectionBinding(collectionId, where, intent);
 
-        val collectionInteractor = objectInteractor.getCollectionInteractor(collectionId, where, intent);
+        collectionBindingOrFailure.right()
+        .ifPresent(InteractionFailureHandler::onFailure);
 
-        val check = collectionInteractor.getCollectionThatIsVisibleForIntent();
-        check.right()
-        .ifPresent(failure->InteractionFailureHandler.onFailure(failure, collectionId));
-
-        return check.leftIfAny();
+        return collectionBindingOrFailure.leftIfAny().getCollection();
     }
 
     public ObjectAction getObjectActionThatIsVisibleForIntent(
             final String actionId, final AccessIntent intent) {
+        
+        val actionBindingOrFailure = objectInteractor.getActionBinding(actionId, where, intent);
 
-        val actionInteractor = objectInteractor.getActionInteractor(actionId, where, intent);
+        actionBindingOrFailure.right()
+        .ifPresent(InteractionFailureHandler::onFailure);
 
-        val check = actionInteractor.getActionThatIsVisibleForIntent();
-        check.right()
-        .ifPresent(failure->InteractionFailureHandler.onFailure(failure, actionId));
+        return actionBindingOrFailure.leftIfAny().getAction();
 
-        return check.leftIfAny();
     }