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/06/24 21:42:43 UTC

[isis] branch master updated: ISIS-2340: wkt: simplify menu building (use shared algorithm)

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


The following commit(s) were added to refs/heads/master by this push:
     new 1ea4aa1  ISIS-2340: wkt: simplify menu building (use shared algorithm)
1ea4aa1 is described below

commit 1ea4aa1642d17f6fd052028e0709047724137b68
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Jun 24 23:42:26 2020 +0200

    ISIS-2340: wkt: simplify menu building (use shared algorithm)
---
 .../viewer/javafx/model/menu/MenuItemFx.java       |  47 ------
 .../viewer/javafx/ui/main/MenuBuilderFx.java       |  12 +-
 .../action/link/ActionLinkUiModelFactory.java      |  43 ------
 .../isis/viewer/common/model/menu/MenuUiModel.java |  28 +---
 .../model/menu/MenuUiModel_buildMenuItems.java     | 116 +++++++++------
 .../model/menu/MenuUiModel_buildMenuItems2.java    | 161 ---------------------
 .../isis/viewer/common/model/menu/MenuVisitor.java |   4 +-
 .../common/model/menuitem/MenuItemUiModel.java     | 121 ----------------
 .../actionmenu/serviceactions/CssMenuItem.java     |  84 +++++------
 .../actionmenu/serviceactions/MenuActionPanel.java |  13 +-
 .../serviceactions/ServiceActionUtil.java          |  57 ++++++--
 .../serviceactions/ServiceActionsPanel.java        |   2 +-
 12 files changed, 165 insertions(+), 523 deletions(-)

diff --git a/incubator/viewers/javafx/model/src/main/java/org/apache/isis/incubator/viewer/javafx/model/menu/MenuItemFx.java b/incubator/viewers/javafx/model/src/main/java/org/apache/isis/incubator/viewer/javafx/model/menu/MenuItemFx.java
deleted file mode 100644
index aacb1b8..0000000
--- a/incubator/viewers/javafx/model/src/main/java/org/apache/isis/incubator/viewer/javafx/model/menu/MenuItemFx.java
+++ /dev/null
@@ -1,47 +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.incubator.viewer.javafx.model.menu;
-
-import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
-
-import lombok.val;
-
-/**
- * @since 2.0.0
- */
-//@Log4j2
-public class MenuItemFx 
-extends MenuItemUiModel<javafx.scene.Node, MenuItemFx> {
-
-    public static MenuItemFx newMenuItem(final String label) {
-        return new MenuItemFx(label);
-    }
-
-    private MenuItemFx(final String label) {
-        super(label);
-    }
-    
-    @Override
-    protected MenuItemFx newSubMenuItem(final String label) {
-        val subMenuItem = newMenuItem(label);
-        subMenuItem.setParent(this);
-        return subMenuItem;
-    }
-
-}
diff --git a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/main/MenuBuilderFx.java b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
index 2e8086d..32acbee 100644
--- a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
+++ b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
@@ -40,18 +40,18 @@ public class MenuBuilderFx implements MenuVisitor {
     private ActionUiModelFactoryFx actionUiModelFactory = new ActionUiModelFactoryFx();
 
     @Override
-    public void addTopLevel(MenuItemDto menu) {
-        log.info("top level menu {}", menu.getName());
+    public void addTopLevel(MenuItemDto menuDto) {
+        log.info("top level menu {}", menuDto.getName());
         
         menuBar.getMenus()
-        .add(currentTopLevelMenu = new Menu(menu.getName()));
+        .add(currentTopLevelMenu = new Menu(menuDto.getName()));
     }
 
     @Override
-    public void addSubMenu(MenuItemDto menu) {
-        val managedAction = menu.getManagedAction();
+    public void addSubMenu(MenuItemDto menuDto) {
+        val managedAction = menuDto.getManagedAction();
         
-        log.info("sub menu {}", menu.getName());
+        log.info("sub menu {}", menuDto.getName());
         
         val actionUiModel = actionUiModelFactory.newActionUiModel(managedAction);
         currentTopLevelMenu.getItems().add(actionUiModel.createMenuUiComponent());
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/link/ActionLinkUiModelFactory.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/link/ActionLinkUiModelFactory.java
deleted file mode 100644
index 297cc21..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/link/ActionLinkUiModelFactory.java
+++ /dev/null
@@ -1,43 +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.action.link;
-
-import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
-
-/**
- * Creates an {@link ActionLinkUiModel}, a holder of the <em>Action's</em> meta-model 
- * and a click-able UI action component; eg. link, button, menu-items.
- * 
- * @see ActionLinkUiModel
- * 
- * @since 2.0.0
- * @param <T> - link component type, native to the viewer
- */
-public interface ActionLinkUiModelFactory<T> {
-    
-    /**
-     * @param named - used when explicitly named (eg. menu bar layout file), otherwise {@code null}
-     * @param managedAction
-     */
-    ActionLinkUiModel<T> newActionLink(
-            String named,
-            ManagedAction managedAction);
-    
-    
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel.java
index 001b826..4c19061 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel.java
@@ -21,14 +21,10 @@ package org.apache.isis.viewer.common.model.menu;
 import java.io.Serializable;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.Consumer;
-import java.util.function.Function;
 
 import org.apache.isis.applib.annotation.DomainServiceLayout;
 import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
 import org.apache.isis.core.runtime.context.IsisAppCommonContext;
-import org.apache.isis.viewer.common.model.action.link.ActionLinkUiModelFactory;
-import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
 
 import lombok.Getter;
 import lombok.NonNull;
@@ -49,28 +45,6 @@ public class MenuUiModel implements Serializable {
         return menuBarSelect.name().toLowerCase(Locale.ENGLISH);
     }
     
-    @Deprecated//use MenuVisitor instead
-    public <T, M extends MenuItemUiModel<T, M>> 
-    void buildMenuItems(
-            final IsisAppCommonContext commonContext,
-            final ActionLinkUiModelFactory<T> menuActionFactory,
-            final Function<String, M> menuItemFactory,
-            final Consumer<M> onNewMenuItem) {
-        
-        val menuBars = commonContext.getMenuBarsService().menuBars();
-
-        // TODO: remove hard-coded dependency on BS3
-        final BS3MenuBar menuBar = (BS3MenuBar) menuBars.menuBarFor(getMenuBarSelect());
-        
-        MenuUiModel_buildMenuItems.buildMenuItems(
-                commonContext, 
-                menuBar,
-                menuActionFactory,
-                menuItemFactory,
-                onNewMenuItem);
-        
-    }
-    
     public void buildMenuItems(
             final IsisAppCommonContext commonContext,
             final MenuVisitor menuBuilder) {
@@ -78,7 +52,7 @@ public class MenuUiModel implements Serializable {
         val menuBars = commonContext.getMenuBarsService().menuBars();
         val menuBar = (BS3MenuBar) menuBars.menuBarFor(getMenuBarSelect());
         
-        MenuUiModel_buildMenuItems2.buildMenuItems(
+        MenuUiModel_buildMenuItems.buildMenuItems(
                 commonContext, 
                 menuBar,
                 menuBuilder);
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems.java
index 7d8ade4..f0b6d91 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems.java
@@ -19,40 +19,36 @@
 package org.apache.isis.viewer.common.model.menu;
 
 import java.util.concurrent.atomic.LongAdder;
-import java.util.function.Consumer;
-import java.util.function.Function;
 
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.layout.component.ServiceActionLayoutData;
 import org.apache.isis.applib.layout.menubars.bootstrap3.BS3Menu;
 import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
 import org.apache.isis.core.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
 import org.apache.isis.core.runtime.context.IsisAppCommonContext;
-import org.apache.isis.viewer.common.model.action.link.ActionLinkUiModelFactory;
-import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
 import org.apache.isis.viewer.common.model.userprofile.UserProfileUiModelProvider;
 
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
-@Log4j2 @Deprecated
+@Log4j2
 final class MenuUiModel_buildMenuItems {
 
-    public static <T, M extends MenuItemUiModel<T, M>> 
-    void buildMenuItems(
-            final IsisAppCommonContext commonContext,
-            final BS3MenuBar menuBar,
-            final ActionLinkUiModelFactory<T> menuActionFactory,
-            final Function<String, M> menuItemFactory,
-            final Consumer<M> onNewMenuItem) {
-
-        // we no longer use ServiceActionsModel#getObject() because the model only holds the services for the
-        // menuBar in question, whereas the "Other" menu may reference a service which is defined for some other menubar
-
+    public static void buildMenuItems(
+            IsisAppCommonContext commonContext, 
+            BS3MenuBar menuBar, 
+            MenuVisitor menuBuilder) {
+        
         val itemsPerSectionCounter = new LongAdder();
         
+        val menuVisitor = MenuProcessor.of(commonContext, menuBuilder); 
+        
         for (val menu : menuBar.getMenus()) {
             
-            val menuItemModel = processTopLevel(commonContext, menuItemFactory, menu);
+            menuVisitor.addTopLevel(menu);
 
             for (val menuSection : menu.getSections()) {
 
@@ -74,41 +70,69 @@ final class MenuUiModel_buildMenuItems {
                         continue;
                     }
                     
-                    val isFirstInSection = itemsPerSectionCounter.intValue()==0; 
-
-                    // Optionally creates a sub-menu item based on visibility and usability
-                    menuItemModel.addSubMenuItemFor(
-                            managedAction, 
-                            isFirstInSection,
-                            newSubMenuItem->{
-                                // increment counter only when a sub item was actually added
-                                itemsPerSectionCounter.increment();
-                                
-                                val menuActionUiModel = menuActionFactory.newActionLink(
-                                        actionLayoutData.getNamed(),
-                                        managedAction);
-                                newSubMenuItem.setMenuActionUiModel(menuActionUiModel);
-                    });
+                    val visibilityVeto = managedAction.checkVisibility(Where.EVERYWHERE);
+                    if (visibilityVeto.isPresent()) {
+                        continue;
+                    }
+                    
+                    val isFirstInSection = itemsPerSectionCounter.intValue()==0;
+                    
+                    menuVisitor.addSubMenu(managedAction, isFirstInSection, actionLayoutData);
+                    itemsPerSectionCounter.increment();
                     
                 }
             }
-            if (menuItemModel.hasSubMenuItems()) {
-                onNewMenuItem.accept(menuItemModel);
-            }
+
         }
-        
     }
     
     // -- HELPER
+    
+    @RequiredArgsConstructor(staticName = "of")
+    private static class MenuProcessor {
+
+        private final IsisAppCommonContext commonContext;
+        private final MenuVisitor menuVisitor;
+        
+        private BS3Menu currentTopLevel;
+        private boolean pushedCurrentTopLevel = false;
+        
+        public void addTopLevel(BS3Menu menu) {
+            currentTopLevel = menu;
+            pushedCurrentTopLevel = false;
+        }
+
+        public void addSubMenu(
+                @NonNull ManagedAction managedAction,
+                boolean isFirstInSection, 
+                ServiceActionLayoutData actionLayoutData) {
+            
+            if(!pushedCurrentTopLevel) {
+                val topLevelDto = topLevelDto(commonContext, currentTopLevel); 
+                
+                menuVisitor.addTopLevel(topLevelDto);
+                pushedCurrentTopLevel = true;
+            } else {
+                if(isFirstInSection) {
+                    menuVisitor.addSectionSpacer();
+                }
+            }
+            val menuDto = MenuItemDto.subMenu(
+                    managedAction,
+                    actionLayoutData.getNamed(), 
+                    actionLayoutData.getCssClassFa());
+            
+            menuVisitor.addSubMenu(menuDto);
+        }
+        
+    }
 
     /**
      * @implNote when ever the top level MenuItem name is empty or {@code null} we set the name
      * to the current user's profile name 
      */
-    private static <T, M extends MenuItemUiModel<T, M>>  
-    M processTopLevel(
+    private static MenuItemDto topLevelDto(
             final IsisAppCommonContext commonContext,
-            final Function<String, M> menuItemFactory, 
             final BS3Menu menu) {
         
         val menuItemIsUserProfile = _Strings.isNullOrEmpty(menu.getNamed()); // top level menu item name
@@ -117,14 +141,10 @@ final class MenuUiModel_buildMenuItems {
                 ? userProfileName(commonContext)
                 : menu.getNamed();
         
-        val menuItemModel = menuItemFactory.apply(menuItemName); 
-        
-        if(menuItemIsUserProfile) {
-            // under the assumption that this can only be the case when we have discovered the empty named top level menu
-            menuItemModel.setTertiaryRoot(true);  
-        }
-        
-        return menuItemModel;
+        return menuItemIsUserProfile
+                // under the assumption that this can only be the case when we have discovered the empty named top level menu
+                ? MenuItemDto.tertiaryRoot(menuItemName, menu.getCssClassFa())
+                : MenuItemDto.topLevel(menuItemName, menu.getCssClassFa());
     }
 
     private static String userProfileName(
@@ -134,6 +154,8 @@ final class MenuUiModel_buildMenuItems {
                 .getUserProfile();
         return userProfile.getUserProfileName();
     }
+
+
     
     
 }
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems2.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems2.java
deleted file mode 100644
index 5799080..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuUiModel_buildMenuItems2.java
+++ /dev/null
@@ -1,161 +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.menu;
-
-import java.util.concurrent.atomic.LongAdder;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.applib.layout.component.ServiceActionLayoutData;
-import org.apache.isis.applib.layout.menubars.bootstrap3.BS3Menu;
-import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
-import org.apache.isis.core.commons.internal.base._Strings;
-import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
-import org.apache.isis.core.runtime.context.IsisAppCommonContext;
-import org.apache.isis.viewer.common.model.userprofile.UserProfileUiModelProvider;
-
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-@Log4j2
-final class MenuUiModel_buildMenuItems2 {
-
-    public static void buildMenuItems(
-            IsisAppCommonContext commonContext, 
-            BS3MenuBar menuBar, 
-            MenuVisitor menuBuilder) {
-        
-        val itemsPerSectionCounter = new LongAdder();
-        
-        val menuVisitor = MenuProcessor.of(commonContext, menuBuilder); 
-        
-        for (val menu : menuBar.getMenus()) {
-            
-            menuVisitor.addTopLevel(menu);
-
-            for (val menuSection : menu.getSections()) {
-
-                itemsPerSectionCounter.reset();
-                
-                for (val actionLayoutData : menuSection.getServiceActions()) {
-                    val serviceSpecId = actionLayoutData.getObjectType();
-
-                    val serviceAdapter = commonContext.lookupServiceAdapterById(serviceSpecId);
-                    if(serviceAdapter == null) {
-                        // service not recognized, presumably the menu layout is out of sync with actual configured modules
-                        continue;
-                    }
-
-                    val managedAction = ManagedAction.lookupAction(serviceAdapter, actionLayoutData.getId())
-                            .orElse(null);
-                    if (managedAction == null) {
-                        log.warn("No such action {}", actionLayoutData.getId());
-                        continue;
-                    }
-                    
-                    val visibilityVeto = managedAction.checkVisibility(Where.EVERYWHERE);
-                    if (visibilityVeto.isPresent()) {
-                        continue;
-                    }
-                    
-                    val isFirstInSection = itemsPerSectionCounter.intValue()==0;
-                    
-                    menuVisitor.addSubMenu(managedAction, isFirstInSection, actionLayoutData);
-                    itemsPerSectionCounter.increment();
-                    
-                }
-            }
-
-        }
-    }
-    
-    // -- HELPER
-    
-    @RequiredArgsConstructor(staticName = "of")
-    private static class MenuProcessor {
-
-        private final IsisAppCommonContext commonContext;
-        private final MenuVisitor menuVisitor;
-        
-        private BS3Menu currentTopLevel;
-        private boolean pushedCurrentTopLevel = false;
-        
-        public void addTopLevel(BS3Menu menu) {
-            currentTopLevel = menu;
-            pushedCurrentTopLevel = false;
-        }
-
-        public void addSubMenu(
-                @NonNull ManagedAction managedAction,
-                boolean isFirstInSection, 
-                ServiceActionLayoutData actionLayoutData) {
-            
-            if(!pushedCurrentTopLevel) {
-                val topLevelDto = topLevelDto(commonContext, currentTopLevel); 
-                
-                menuVisitor.addTopLevel(topLevelDto);
-                pushedCurrentTopLevel = true;
-            } else {
-                if(isFirstInSection) {
-                    menuVisitor.addSectionSpacer();
-                }
-            }
-            val menuDto = MenuItemDto.subMenu(
-                    managedAction,
-                    actionLayoutData.getNamed(), 
-                    actionLayoutData.getCssClassFa());
-            
-            menuVisitor.addSubMenu(menuDto);
-        }
-        
-    }
-
-    /**
-     * @implNote when ever the top level MenuItem name is empty or {@code null} we set the name
-     * to the current user's profile name 
-     */
-    private static MenuItemDto topLevelDto(
-            final IsisAppCommonContext commonContext,
-            final BS3Menu menu) {
-        
-        val menuItemIsUserProfile = _Strings.isNullOrEmpty(menu.getNamed()); // top level menu item name
-            
-        val menuItemName = menuItemIsUserProfile
-                ? userProfileName(commonContext)
-                : menu.getNamed();
-        
-        return menuItemIsUserProfile
-                // under the assumption that this can only be the case when we have discovered the empty named top level menu
-                ? MenuItemDto.tertiaryRoot(menuItemName, menu.getCssClassFa())
-                : MenuItemDto.topLevel(menuItemName, menu.getCssClassFa());
-    }
-
-    private static String userProfileName(
-            final IsisAppCommonContext commonContext) {
-        val userProfile = commonContext
-                .lookupServiceElseFail(UserProfileUiModelProvider.class)
-                .getUserProfile();
-        return userProfile.getUserProfileName();
-    }
-
-
-    
-    
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuVisitor.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuVisitor.java
index 9cf198a..e2df4b4 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuVisitor.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuVisitor.java
@@ -20,8 +20,8 @@ package org.apache.isis.viewer.common.model.menu;
 
 public interface MenuVisitor {
 
-    void addTopLevel(MenuItemDto menu);
+    void addTopLevel(MenuItemDto menuDto);
     void addSectionSpacer();
-    void addSubMenu(MenuItemDto menu);
+    void addSubMenu(MenuItemDto menuDto);
     
 }
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java
deleted file mode 100644
index 9603f0d..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java
+++ /dev/null
@@ -1,121 +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.menuitem;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-import javax.annotation.Nullable;
-
-import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.core.commons.internal.base._Casts;
-import org.apache.isis.core.commons.internal.collections._Lists;
-import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
-import org.apache.isis.viewer.common.model.action.ActionUiMetaModel;
-import org.apache.isis.viewer.common.model.action.link.ActionLinkUiModel;
-
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.Setter;
-import lombok.val;
-import lombok.experimental.Accessors;
-import lombok.extern.log4j.Log4j2;
-
-/**
- * 
- * @since 2.0.0
- * @param <T> - link component type, native to the viewer
- * @param <U> - concrete type implementing this class
- */
-@Accessors(chain = true)
-@RequiredArgsConstructor
-@Log4j2
-public abstract class MenuItemUiModel<T, U extends MenuItemUiModel<T, U>> {
-    
-    @Getter private final String name;
-
-    /**
-     * To determine whether requires a separator before it.
-     */
-    @Getter @Setter private boolean isFirstInSection = false; // unless set otherwise
-    
-    @Getter @Setter private boolean isTertiaryRoot = false; // unless set otherwise
-
-    @Getter @Setter private ActionLinkUiModel<T> menuActionUiModel;
-    
-    private final List<U> subMenuItems = _Lists.newArrayList();
-    protected void addSubMenuItem(final U cssMenuItem) {
-        subMenuItems.add(cssMenuItem);
-    }
-    public List<U> getSubMenuItems() {
-        return Collections.unmodifiableList(subMenuItems);
-    }
-    /**
-     * @param menuItems we assume these have the correct parent already set
-     */
-    public void replaceSubMenuItems(List<U> menuItems) {
-        subMenuItems.clear();
-        subMenuItems.addAll(menuItems);
-    }
-    public boolean hasSubMenuItems() {
-        return subMenuItems.size() > 0;
-    }
-    
-    
-    @Getter private U parent;
-    protected void setParent(U parent) {
-        this.parent = parent;
-        parent.addSubMenuItem(_Casts.uncheckedCast(this));        
-    }
-    public boolean hasParent() {
-        return parent != null;
-    }
-    
-    // -- CONSTRUCTION
-    
-    /**
-     * Optionally creates a sub-menu item invoking an action on the provided 
-     * {@link MenuActionWkt action model}, based on visibility and usability.
-     */
-    public void addSubMenuItemFor(
-            @NonNull final ManagedAction managedAction,
-            final boolean isFirstInSection,
-            @Nullable final Consumer<U> onNewSubMenuItem) {
-
-        if(managedAction.checkUsability(Where.EVERYWHERE).isPresent()) {
-            log.debug("not visible {}", managedAction.getName());
-            return;
-        }
-        
-        val actionMeta = ActionUiMetaModel.of(managedAction);
-
-        val menutIem = newSubMenuItem(actionMeta.getLabel())
-                .setFirstInSection(isFirstInSection);
-        
-        if(onNewSubMenuItem!=null) {
-            onNewSubMenuItem.accept(_Casts.uncheckedCast(menutIem));
-        }
-    }
-    
-    protected abstract U newSubMenuItem(final String name);
-
-    
-}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
index a55116f..11f8209 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
@@ -16,30 +16,29 @@
  * under the License. */
 package org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions;
 
-import java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
 import java.io.Serializable;
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.model.Model;
 
-import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
+import org.apache.isis.core.commons.internal.base._Casts;
+import org.apache.isis.core.commons.internal.collections._Lists;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.ui.util.Components;
 import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
 import org.apache.isis.viewer.wicket.ui.util.Decorators;
 import org.apache.isis.viewer.wicket.ui.util.Tooltips;
 
+import lombok.Getter;
+import lombok.Setter;
 import lombok.val;
 
 class CssMenuItem 
-extends MenuItemUiModel<AbstractLink, CssMenuItem> 
 implements Serializable {
 
     private static final long serialVersionUID = 1L;
@@ -51,20 +50,37 @@ implements Serializable {
         return new CssMenuItem(name);
     }
 
+    @Getter private final String name;
+    
+    @Getter @Setter private LinkAndLabel linkAndLabel;
+    @Getter @Setter private boolean needsSpacerBeforeSelf;
+    
     private CssMenuItem(final String name) {
-        super(name);
+        this.name = name;
     }
     
-    @Override
     protected CssMenuItem newSubMenuItem(final String name) {
         val subMenuItem = newMenuItem(name);
         subMenuItem.setParent(this);
         return subMenuItem;
     }
     
-    @Override
-    public LinkAndLabel getMenuActionUiModel() {
-        return (LinkAndLabel) super.getMenuActionUiModel();
+    private final List<CssMenuItem> subMenuItems = _Lists.newArrayList();
+    protected void addSubMenuItem(final CssMenuItem cssMenuItem) {
+        subMenuItems.add(cssMenuItem);
+    }
+    public List<CssMenuItem> getSubMenuItems() {
+        return Collections.unmodifiableList(subMenuItems);
+    }
+    /**
+     * @param menuItems we assume these have the correct parent already set
+     */
+    public void replaceSubMenuItems(List<CssMenuItem> menuItems) {
+        subMenuItems.clear();
+        subMenuItems.addAll(menuItems);
+    }
+    public boolean hasSubMenuItems() {
+        return subMenuItems.size() > 0;
     }
     
     // //////////////////////////////////////////////////////////////
@@ -81,8 +97,8 @@ implements Serializable {
 
     private Component addMenuItemComponentTo(final MarkupContainer markupContainer) {
         
-        val actionMeta = super.getMenuActionUiModel().getActionUiMetaModel();
-        val actionLink = super.getMenuActionUiModel().getUiComponent();
+        val actionMeta = getLinkAndLabel().getActionUiMetaModel();
+        val actionLink = getLinkAndLabel().getUiComponent();
         
         val label = new Label(CssMenuItem.ID_MENU_LABEL, Model.of(this.getName()));
 
@@ -153,43 +169,19 @@ implements Serializable {
             linkComponent.add(new CssClassAppender("top-parent"));
         }
     }
+    
+    @Getter private CssMenuItem parent;
+    protected void setParent(CssMenuItem parent) {
+        this.parent = parent;
+        parent.addSubMenuItem(_Casts.uncheckedCast(this));        
+    }
+    public boolean hasParent() {
+        return parent != null;
+    }
 
-    // -- SERIALIZATION PROXY
 
-    private Object writeReplace() {
-        return new SerializationProxy(this);
-    }
 
-    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
-        throw new InvalidObjectException("Proxy required");
-    }
 
-    private static class SerializationProxy implements Serializable {
-        private static final long serialVersionUID = 1L;
-        private final String name;
-        private final LinkAndLabel menuActionUiModel;
-        private final boolean firstInSection;
-        private final boolean tertiaryRoot;
-        //private final CssMenuItem parent;
-        private List<CssMenuItem> children;
-        
-        public SerializationProxy(CssMenuItem menuItem) {
-            this.name = menuItem.getName();
-            this.menuActionUiModel = menuItem.getMenuActionUiModel();
-            this.firstInSection = menuItem.isFirstInSection();
-            this.tertiaryRoot = menuItem.isTertiaryRoot();
-            //this.parent = menuItem.getParent();
-            this.children = new ArrayList<CssMenuItem>(menuItem.getSubMenuItems());
-        }
-        private Object readResolve() {
-            val menuItem = new CssMenuItem(name);
-            menuItem.setFirstInSection(firstInSection);
-            menuItem.setTertiaryRoot(tertiaryRoot);
-            menuItem.setMenuActionUiModel(menuActionUiModel);
-            for (CssMenuItem child : children) menuItem.addSubMenuItem(child);
-            return menuItem;
-        }
-    }
 
 
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/MenuActionPanel.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/MenuActionPanel.java
index c7bd1a6..b42debd 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/MenuActionPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/MenuActionPanel.java
@@ -43,16 +43,14 @@ public abstract class MenuActionPanel extends PanelBase {
         return new ListView<CssMenuItem>("subMenuItems", subMenuItems) {
 
             private static final long serialVersionUID = 1L;
-            private transient int populationCount = 0;
 
             @Override
             protected void populateItem(ListItem<CssMenuItem> listItem) {
                 val subMenuItem = listItem.getModelObject();
 
-                if(subMenuItem.isFirstInSection() 
-                        && populationCount>0) {
-                    addSeparatorBefore(subMenuItem, listItem);
-                }
+                if(subMenuItem.isNeedsSpacerBeforeSelf()) {
+                    listItem.add(new CssClassAppender("list-separator"));
+                } 
 
                 if (subMenuItem.hasSubMenuItems()) {
                     addFolderItem(subMenuItem, listItem);
@@ -60,7 +58,6 @@ public abstract class MenuActionPanel extends PanelBase {
                     addLeafItem(subMenuItem, listItem);
                 }
 
-                populationCount++;
             }
         };
     }
@@ -76,10 +73,6 @@ public abstract class MenuActionPanel extends PanelBase {
 
     // -- HELPER
 
-    private void addSeparatorBefore(CssMenuItem menuItem, ListItem<CssMenuItem> listItem) {
-        listItem.add(new CssClassAppender("list-separator"));
-    }
-
     private void addFolderItem(CssMenuItem menuItem, ListItem<CssMenuItem> listItem) {
         final MarkupContainer parent = this;
         ServiceActionUtil.addFolderItem(super.getCommonContext(), menuItem, listItem, parent);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
index 357243f..8ff5986 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
@@ -24,15 +24,15 @@ import java.util.function.Consumer;
 
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Fragment;
 
 import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
 import org.apache.isis.core.runtime.context.IsisAppCommonContext;
-import org.apache.isis.viewer.common.model.action.link.ActionLinkUiModelFactory;
+import org.apache.isis.viewer.common.model.menu.MenuItemDto;
 import org.apache.isis.viewer.common.model.menu.MenuUiModel;
+import org.apache.isis.viewer.common.model.menu.MenuVisitor;
 import org.apache.isis.viewer.wicket.model.common.CommonContextUtils;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
@@ -54,7 +54,7 @@ public final class ServiceActionUtil {
             ListItem<CssMenuItem> listItem,
             MarkupContainer parent) {
         
-        val actionUiModel = menuItem.getMenuActionUiModel();
+        val actionUiModel = menuItem.getLinkAndLabel();
         val menuItemActionLink = actionUiModel.getUiComponent();
 
         val menuItemLabel = new Label("menuLinkLabel", menuItem.getName());
@@ -87,7 +87,7 @@ public final class ServiceActionUtil {
         Fragment folderItem = new Fragment("content", "folderItem", parent);
         listItem.add(folderItem);
 
-        folderItem.add(new Label("folderName", subMenuItem.getMenuActionUiModel().getLabel()));
+        folderItem.add(new Label("folderName", subMenuItem.getLinkAndLabel().getLabel()));
         final List<CssMenuItem> menuItems = subMenuItem.getSubMenuItems();
         ListView<CssMenuItem> subMenuItemsView = new ListView<CssMenuItem>("subMenuItems",
                 menuItems) {
@@ -107,12 +107,11 @@ public final class ServiceActionUtil {
         folderItem.add(subMenuItemsView);
     }
 
-    @RequiredArgsConstructor
-    private static class MenuActionFactoryWkt implements ActionLinkUiModelFactory<AbstractLink> {
+    @RequiredArgsConstructor(staticName = "of")
+    private static class LinkAndLabelFactoryWkt {
 
         private final IsisAppCommonContext commonContext;
         
-        @Override
         public LinkAndLabel newActionLink(
                 final String named, 
                 final ManagedAction managedAction) {
@@ -135,17 +134,51 @@ public final class ServiceActionUtil {
         
     }
 
+    @RequiredArgsConstructor(staticName = "of")
+    private static class MenuBuilderWkt implements MenuVisitor {
+        
+        private final Consumer<CssMenuItem> onNewMenuItem;
+        private final LinkAndLabelFactoryWkt linkAndLabelFactory;
+        
+        private CssMenuItem currentTopLevelMenu = null;
+        private boolean needsSpacerBeforeSelf = false;
+        
+        @Override
+        public void addTopLevel(MenuItemDto menuDto) {
+            currentTopLevelMenu = CssMenuItem.newMenuItem(menuDto.getName());
+            onNewMenuItem.accept(currentTopLevelMenu);
+        }
+
+        @Override
+        public void addSectionSpacer() {
+            needsSpacerBeforeSelf = true;
+        }
 
+        @Override
+        public void addSubMenu(MenuItemDto menuDto) {
+            val managedAction = menuDto.getManagedAction();
+            
+            val menuItem = CssMenuItem.newMenuItem(menuDto.getName());
+            
+            currentTopLevelMenu.addSubMenuItem(menuItem);
+            
+            menuItem.setLinkAndLabel(linkAndLabelFactory.newActionLink(menuDto.getName(), managedAction));
+            if(needsSpacerBeforeSelf) {
+                needsSpacerBeforeSelf = false;
+                menuItem.setNeedsSpacerBeforeSelf(true);
+            }
+        }
+        
+    }
+    
     public static void buildMenu(
             final IsisAppCommonContext commonContext,
             final MenuUiModel menuUiModel,
             final Consumer<CssMenuItem> onNewMenuItem) {
         
-        menuUiModel.buildMenuItems(
-                commonContext, 
-                new MenuActionFactoryWkt(commonContext),
-                CssMenuItem::newMenuItem,
-                onNewMenuItem);
+        menuUiModel.buildMenuItems(commonContext, MenuBuilderWkt.of(
+                onNewMenuItem,
+                LinkAndLabelFactoryWkt.of(commonContext)));
     }
 
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanel.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanel.java
index 444f536..e97f244 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanel.java
@@ -99,7 +99,7 @@ public class ServiceActionsPanel extends MenuActionPanel {
         final List<CssMenuItem> childItems = menuItem.getSubMenuItems();
         return _NullSafe.stream(childItems) 
                 .map((final CssMenuItem input) -> {
-                    final String actionIdentifier = input.getMenuActionUiModel().getActionUiMetaModel().getActionIdentifier();
+                    final String actionIdentifier = input.getLinkAndLabel().getActionUiMetaModel().getActionIdentifier();
                     if (actionIdentifier != null) {
                         // busrules-busrulesobjects-findbyname
                         final String actionId = CssClassAppender.asCssStyle(actionIdentifier);