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 2021/07/07 05:56:55 UTC

[isis] branch master updated: ISIS-2787: adds support for runtime/dynamic reloading of menubars.xml

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 7caa1be  ISIS-2787: adds support for runtime/dynamic reloading of menubars.xml
7caa1be is described below

commit 7caa1be0ce6222c65c02fa7fb3faf21bdd8c0ced
Author: andi-huber <ah...@apache.org>
AuthorDate: Wed Jul 7 07:54:57 2021 +0200

    ISIS-2787: adds support for runtime/dynamic reloading of menubars.xml
---
 .../isis/core/metamodel/facetapi/FacetRanking.java | 42 ++++++++++++++++++----
 .../isis/core/metamodel/facetapi/FacetUtil.java    | 14 ++++++++
 .../menubars/bootstrap3/MenuBarsServiceBS3.java    | 11 ++++++
 .../bootstrap3/MenuBarsServiceBS3Test.java         | 24 +++++++++++++
 4 files changed, 85 insertions(+), 6 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetRanking.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetRanking.java
index f0c8e13..eda3d69 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetRanking.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetRanking.java
@@ -23,10 +23,12 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.base._Reduction;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Multimaps;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
@@ -186,14 +188,12 @@ public final class FacetRanking {
      * @param facetType - for convenience, so the caller does not need to cast the result
      * @param precedenceUpper - upper bound
      */
-    public <F extends Facet> Can<F> getRankLowerOrEqualTo(final @NonNull Class<F> facetType, final @NonNull Precedence precedenceUpper) {
+    public <F extends Facet> Can<F> getRankLowerOrEqualTo(
+            final @NonNull Class<F> facetType,
+            final @NonNull Precedence precedenceUpper) {
         _Assert.assertEquals(this.facetType, facetType);
 
-        val precedenceSelected = facetsByPrecedence
-        .keySet()
-        .stream()
-        .filter(precedence->precedence.ordinal()<=precedenceUpper.ordinal())
-        .max(Comparator.comparing(Precedence::ordinal));
+        val precedenceSelected = getHighestPrecedenceLowerOrEqualTo(precedenceUpper);
 
         return precedenceSelected
         .map(facetsByPrecedence::get)
@@ -217,6 +217,36 @@ public final class FacetRanking {
         return Optional.ofNullable(topPrecedenceRef.get());
     }
 
+    // -- DYNAMIC UPDATE SUPPORT
+
+    /**
+     * Removes any facet of {@code facetType} from facetHolder if it passes the given {@code filter}.
+     * @param facetType - to ensure the filter is properly generic-type-constraint
+     * @param filter
+     */
+    public <F extends Facet> void purgeIf(
+            final @NonNull Class<F> facetType,
+            final @NonNull Predicate<? extends F> filter) {
+
+        // reassess the top precedence
+        final _Reduction<Facet.Precedence> top = _Reduction.of(null, (a, b)->a==null?b:a.ordinal()>b.ordinal()?a:b);
+        val markedForRemoval = _Lists.newArrayList(facetsByPrecedence.size());
+
+        facetsByPrecedence.forEach((precedence, facets)->{
+            facets.removeIf(_Casts.uncheckedCast(filter));
+            if(!facets.isEmpty()) {
+                top.accept(precedence);
+            } else {
+                markedForRemoval.add(precedence);
+            }
+        });
+
+        topPrecedenceRef.set(top.getResult().orElse(null));
+
+        // remove keys that associate empty lists, so finding highest used precedence by key is simple
+        markedForRemoval.forEach(facetsByPrecedence::remove);
+    }
+
     // -- VALIDATION SUPPORT
 
     public <F extends Facet> void visitTopRankPairsSemanticDiffering(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetUtil.java
index 92f2449..139d295 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetUtil.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facetapi;
 
 import java.util.Optional;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 
 import javax.annotation.Nullable;
 
@@ -97,4 +98,17 @@ public final class FacetUtil {
         .forEach(target::addFacet);
     }
 
+    /**
+     * removes any facet of facet-type from facetHolder if it passes the given filter
+     */
+    public static <F extends Facet> void purgeIf(
+            final Class<F> facetType,
+            final Predicate<? extends F> filter,
+            final FacetHolder facetHolder) {
+
+        facetHolder.getFacetRanking(facetType)
+        .ifPresent(ranking->ranking.purgeIf(facetType, filter));
+    }
+
+
 }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
index e68eeaa..4f31074 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
@@ -58,6 +58,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facets.actions.layout.MemberDescribedFacetForMenuBarXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.MemberNamedFacetForMenuBarXml;
 import org.apache.isis.core.metamodel.facets.actions.notinservicemenu.NotInServiceMenuFacet;
+import org.apache.isis.core.metamodel.facets.all.described.MemberDescribedFacet;
 import org.apache.isis.core.metamodel.facets.all.i8n.staatic.HasStaticText;
 import org.apache.isis.core.metamodel.facets.all.named.MemberNamedFacet;
 import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFacet;
@@ -194,6 +195,16 @@ implements MenuBarsService {
 
                 val layoutData = serviceActionLayoutDataByActionId.get(actionId);
 
+                FacetUtil.purgeIf(
+                        MemberNamedFacet.class,
+                        facet->facet instanceof MemberNamedFacetForMenuBarXml,
+                        objectAction);
+
+                FacetUtil.purgeIf(
+                        MemberDescribedFacet.class,
+                        facet->facet instanceof MemberDescribedFacetForMenuBarXml,
+                        objectAction);
+
                 FacetUtil.addFacetIfPresent(
                         MemberNamedFacetForMenuBarXml
                         .create(layoutData, objectAction));
diff --git a/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3Test.java b/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3Test.java
index a6e7512..e8093f1 100644
--- a/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3Test.java
+++ b/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3Test.java
@@ -27,11 +27,14 @@ import org.apache.isis.applib.services.layout.LayoutService;
 import org.apache.isis.applib.services.menu.MenuBarsLoaderService;
 import org.apache.isis.applib.services.menu.MenuBarsService;
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting.MetaModelContext_forTestingBuilder;
+import org.apache.isis.core.metamodel.facetapi.Facet.Precedence;
+import org.apache.isis.core.metamodel.facets.all.named.MemberNamedFacet;
 import org.apache.isis.core.runtimeservices.RuntimeServicesTestAbstract;
 import org.apache.isis.core.runtimeservices.menubars.MenuBarsLoaderServiceDefault;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import lombok.val;
@@ -126,6 +129,27 @@ extends RuntimeServicesTestAbstract {
         assertNotNull(objectAction);
 
         assertEquals(customNamed, objectAction.getStaticFriendlyName().orElse(null));
+
+        // trigger a menubars.xml reload, which installs more facets while purging old ones
+        // verify however, that the number of facets stays constant
+
+        menuBarsService.menuBars(); // trigger reload (MenuBarsService for testing is setup the support reloading)
+
+        assertEquals(customNamed, objectAction.getStaticFriendlyName().orElse(null));
+
+        val facetRanking = objectAction.getFacetRanking(MemberNamedFacet.class).orElse(null);
+        assertNotNull(facetRanking);
+
+        // XML layout facets are installed at precedence HIGH
+        val xmlFacetRank = facetRanking.getRankLowerOrEqualTo(MemberNamedFacet.class, Precedence.HIGH);
+
+        // verify rank did not grow with latest menubars.xml reload
+        assertEquals(1, xmlFacetRank.size());
+
+        // verify winning facet is the same object as the last one added from latest menubars.xml reload,
+        // to make sure we are not feed the winner from an outdated cache
+        assertSame(facetRanking.getWinnerNonEvent(MemberNamedFacet.class).get(), xmlFacetRank.getLastOrFail());
+
     }
 
     // -- HELPER