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

ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/4_0_X by this push:
     new c1c3c807ff [SYNCOPE-1960] Remove related attributes when removing aux 
classes
c1c3c807ff is described below

commit c1c3c807ff11fbb6c9578902379c1cbe602b307f
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Apr 8 09:52:47 2026 +0200

    [SYNCOPE-1960] Remove related attributes when removing aux classes
---
 .../client/console/wizards/any/Resources.java      | 49 ++++++++++++-
 .../wizards/any/AbstractAnyWizardBuilder.java      | 56 ---------------
 .../ui/commons/wizards/any/AbstractAuxClasses.java | 72 -------------------
 .../ui/commons/wizards/any/AbstractGroups.java     | 81 ----------------------
 .../ui/commons/wizards/any/AbstractResources.java  | 73 -------------------
 .../syncope/client/console/panels/AnyPanel.java    |  3 +-
 .../wizards/any/AnyObjectWizardBuilder.java        |  7 +-
 .../console/wizards/any/AnyWizardBuilder.java      | 32 ++-------
 .../console/wizards/any/ConsoleAuxClasses.java     | 44 ++++++++++--
 .../console/wizards/any/GroupWizardBuilder.java    |  7 +-
 .../syncope/client/console/wizards/any/Groups.java | 47 +++++++++++--
 .../core/provisioning/java/data/AnyDataBinder.java | 35 +++++-----
 .../java/data/AnyObjectDataBinderImpl.java         |  2 +-
 .../java/data/GroupDataBinderImpl.java             |  2 +-
 .../provisioning/java/data/UserDataBinderImpl.java |  2 +-
 .../apache/syncope/fit/core/AnyObjectITCase.java   | 34 +++++++++
 pom.xml                                            |  2 +-
 17 files changed, 195 insertions(+), 353 deletions(-)

diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/Resources.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/Resources.java
index db2aeec635..029b76b97a 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/Resources.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/Resources.java
@@ -18,20 +18,63 @@
  */
 package org.apache.syncope.client.console.wizards.any;
 
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeWebApplication;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractResources;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.IdMEntitlement;
 import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.ActionPermissions;
 import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.wizard.WizardModel;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.util.ListModel;
 
-public class Resources extends AbstractResources {
+public class Resources extends WizardStep implements WizardModel.ICondition {
 
     private static final long serialVersionUID = 702900610508752856L;
 
+    protected final AnyTO anyTO;
+
+    protected final ListModel<String> available;
+
     public <T extends AnyTO> Resources(final AnyWrapper<T> modelObject) {
-        super(modelObject);
+        anyTO = modelObject.getInnerObject();
+
+        if (modelObject instanceof UserWrapper userWrapper
+                && userWrapper.getPreviousUserTO() != null
+                && !modelObject.getInnerObject().getResources().equals(
+                        userWrapper.getPreviousUserTO().getResources())) {
+
+            add(new LabelInfo("changed", StringUtils.EMPTY));
+        } else {
+            add(new Label("changed", StringUtils.EMPTY));
+        }
+
+        this.setOutputMarkupId(true);
+        this.available = new ListModel<>(List.of());
+
+        add(new AjaxPalettePanel.Builder<String>().build("resources", new 
PropertyModel<>(anyTO, "resources") {
+
+            private static final long serialVersionUID = 3799387950428254072L;
+
+            @Override
+            public List<String> getObject() {
+                return new ArrayList<>(anyTO.getResources());
+            }
+
+            @Override
+            public void setObject(final List<String> object) {
+                anyTO.getResources().clear();
+                anyTO.getResources().addAll(object);
+            }
+        }, available).hideLabel().setOutputMarkupId(true));
 
         // -----------------------------------------------------------------
         // Pre-Authorizations
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAnyWizardBuilder.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAnyWizardBuilder.java
deleted file mode 100644
index 01d5dd70ef..0000000000
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAnyWizardBuilder.java
+++ /dev/null
@@ -1,56 +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.syncope.client.ui.commons.wizards.any;
-
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizardBuilder;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.GroupableRelatableTO;
-import org.apache.wicket.PageReference;
-
-public abstract class AbstractAnyWizardBuilder<A extends AnyTO> extends 
AjaxWizardBuilder<AnyWrapper<A>> {
-
-    private static final long serialVersionUID = -2480279868319546243L;
-
-    public AbstractAnyWizardBuilder(final AnyWrapper<A> defaultItem, final 
PageReference pageRef) {
-        super(defaultItem, pageRef);
-    }
-
-    protected void fixPlainAttrs(final AnyTO updated, final AnyTO original) {
-        // re-add to the updated object any missing plain attribute (compared 
to original): this to cope with
-        // form layout, which might have not included some plain attributes
-        original.getPlainAttrs().stream().
-                filter(attr -> 
updated.getPlainAttr(attr.getSchema()).isPresent()).
-                forEach(attr -> updated.getPlainAttrs().add(attr));
-        if (updated instanceof GroupableRelatableTO updatedTO && original 
instanceof GroupableRelatableTO originalTO) {
-            originalTO.getMemberships().
-                    forEach(oMemb -> 
updatedTO.getMembership(oMemb.getGroupKey()).
-                    ifPresent(uMemb -> oMemb.getPlainAttrs().stream().
-                    filter(attr -> 
uMemb.getPlainAttr(attr.getSchema()).isPresent()).
-                    forEach(attr -> uMemb.getPlainAttrs().add(attr))));
-        }
-
-        // remove from the updated object any plain attribute without values, 
thus triggering for removal in
-        // the generated patch
-        updated.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty());
-        if (updated instanceof GroupableRelatableTO updatedTO) {
-            updatedTO.getMemberships().
-                    forEach(memb -> memb.getPlainAttrs().removeIf(attr -> 
attr.getValues().isEmpty()));
-        }
-    }
-}
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAuxClasses.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAuxClasses.java
deleted file mode 100644
index 4df345008f..0000000000
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractAuxClasses.java
+++ /dev/null
@@ -1,72 +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.syncope.client.ui.commons.wizards.any;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.apache.commons.collections4.ListUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.AnyTypeClassTO;
-import org.apache.wicket.extensions.wizard.WizardStep;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.model.PropertyModel;
-import org.apache.wicket.model.util.ListModel;
-
-public abstract class AbstractAuxClasses extends WizardStep {
-
-    private static final long serialVersionUID = 552437609667518888L;
-
-    public <T extends AnyTO> AbstractAuxClasses(final AnyWrapper<T> 
modelObject, final List<String> anyTypeClasses) {
-        super();
-        setOutputMarkupId(true);
-
-        List<AnyTypeClassTO> allAnyTypeClasses = listAnyTypecClasses();
-
-        List<String> choices = new ArrayList<>();
-        for (AnyTypeClassTO aux : allAnyTypeClasses) {
-            if (!anyTypeClasses.contains(aux.getKey())) {
-                choices.add(aux.getKey());
-            }
-        }
-        Collections.sort(choices);
-        add(new 
AjaxPalettePanel.Builder<String>().setAllowOrder(true).build("auxClasses",
-                new PropertyModel<>(modelObject.getInnerObject(), 
"auxClasses"),
-                new ListModel<>(choices)).hideLabel().setOutputMarkupId(true));
-
-        // ------------------
-        // insert changed label if needed
-        // ------------------
-        if (modelObject instanceof UserWrapper
-                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != 
null
-                && !ListUtils.isEqualList(
-                        modelObject.getInnerObject().getAuxClasses(),
-                        
UserWrapper.class.cast(modelObject).getPreviousUserTO().getAuxClasses())) {
-            add(new LabelInfo("changed", StringUtils.EMPTY));
-        } else {
-            add(new Label("changed", StringUtils.EMPTY));
-        }
-        // ------------------
-    }
-
-    protected abstract List<AnyTypeClassTO> listAnyTypecClasses();
-}
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
deleted file mode 100644
index 45c9fdf27d..0000000000
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
+++ /dev/null
@@ -1,81 +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.syncope.client.ui.commons.wizards.any;
-
-import org.apache.commons.collections4.ListUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
-import org.apache.wicket.extensions.wizard.WizardStep;
-import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.html.basic.Label;
-
-public abstract class AbstractGroups extends WizardStep implements ICondition {
-
-    private static final long serialVersionUID = 552437609667518888L;
-
-    protected static final int MAX_GROUP_LIST_CARDINALITY = 30;
-
-    protected final AnyTO anyTO;
-
-    protected WebMarkupContainer dyngroupsContainer;
-
-    protected WebMarkupContainer dynrealmsContainer;
-
-    protected WebMarkupContainer groupsContainer;
-
-    public <T extends AnyTO> AbstractGroups(final AnyWrapper<T> modelObject) {
-        super();
-        this.anyTO = modelObject.getInnerObject();
-
-        setOutputMarkupId(true);
-
-        groupsContainer = new WebMarkupContainer("groupsContainer");
-        groupsContainer.setOutputMarkupId(true);
-        groupsContainer.setOutputMarkupPlaceholderTag(true);
-        add(groupsContainer);
-
-        // ------------------
-        // insert changed label if needed
-        // ------------------
-        if (modelObject instanceof final UserWrapper uw
-                && uw.getPreviousUserTO() != null
-                && !ListUtils.isEqualList(
-                        uw.getInnerObject().getMemberships(),
-                        uw.getPreviousUserTO().getMemberships())) {
-
-            groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY));
-        } else {
-            groupsContainer.add(new Label("changed", StringUtils.EMPTY));
-        }
-        // ------------------
-    }
-
-    protected abstract void addGroupsPanel();
-
-    protected abstract void addDynamicRealmsContainer();
-
-    protected abstract void addDynamicGroupsContainer();
-
-    @Override
-    public boolean evaluate() {
-        return true;
-    }
-}
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractResources.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractResources.java
deleted file mode 100644
index f6fde2dd45..0000000000
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractResources.java
+++ /dev/null
@@ -1,73 +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.syncope.client.ui.commons.wizards.any;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
-import org.apache.wicket.extensions.wizard.WizardStep;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.model.PropertyModel;
-import org.apache.wicket.model.util.ListModel;
-
-public abstract class AbstractResources extends WizardStep implements 
ICondition {
-
-    private static final long serialVersionUID = 552437609667518888L;
-
-    protected final AnyTO anyTO;
-
-    protected final ListModel<String> available;
-
-    public <T extends AnyTO> AbstractResources(final AnyWrapper<T> 
modelObject) {
-        anyTO = modelObject.getInnerObject();
-
-        if (modelObject instanceof UserWrapper userWrapper
-                && userWrapper.getPreviousUserTO() != null
-                && !modelObject.getInnerObject().getResources().equals(
-                        userWrapper.getPreviousUserTO().getResources())) {
-
-            add(new LabelInfo("changed", StringUtils.EMPTY));
-        } else {
-            add(new Label("changed", StringUtils.EMPTY));
-        }
-
-        this.setOutputMarkupId(true);
-        this.available = new ListModel<>(List.of());
-
-        add(new AjaxPalettePanel.Builder<String>().build("resources", new 
PropertyModel<>(anyTO, "resources") {
-
-            private static final long serialVersionUID = 3799387950428254072L;
-
-            @Override
-            public List<String> getObject() {
-                return new ArrayList<>(anyTO.getResources());
-            }
-
-            @Override
-            public void setObject(final List<String> object) {
-                anyTO.getResources().clear();
-                anyTO.getResources().addAll(object);
-            }
-        }, available).hideLabel().setOutputMarkupId(true));
-    }
-}
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
index 7abaa5f20b..7851d41167 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
@@ -286,8 +286,7 @@ public class AnyPanel extends Panel implements ModalPanel {
                             
pr).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
                             
setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(
                             AnyLayoutUtils.newLayoutInfo(anyObject, 
anyType.getClasses(),
-                                    
layout.getAnyObjects().get(anyType.getKey()),
-                                    anyObjectRestClient, pr)).
+                                    
layout.getAnyObjects().get(anyType.getKey()), anyObjectRestClient, pr)).
                             build(id);
                     MetaDataRoleAuthorizationStrategy.authorize(
                             panel, WebPage.RENDER, 
AnyEntitlement.SEARCH.getFor(anyType.getKey()));
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java
index 5771d4af9c..a853252249 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java
@@ -47,8 +47,8 @@ public class AnyObjectWizardBuilder extends 
AnyWizardBuilder<AnyObjectTO> implem
             final AnyObjectRestClient anyObjectRestClient,
             final PageReference pageRef) {
 
-        super(Optional.ofNullable(anyObjectTO).map(AnyObjectWrapper::new).
-                orElse(null), anyTypeClasses, formLayoutInfo, pageRef);
+        
super(Optional.ofNullable(anyObjectTO).map(AnyObjectWrapper::new).orElse(null),
+                anyTypeClasses, formLayoutInfo, pageRef);
         this.anyObjectRestClient = anyObjectRestClient;
     }
 
@@ -68,7 +68,8 @@ public class AnyObjectWizardBuilder extends 
AnyWizardBuilder<AnyObjectTO> implem
             final AnyObjectFormLayoutInfo formLayoutInfo,
             final PageReference pageRef) {
 
-        super(new AnyObjectWrapper(previousAnyObjectTO, anyObjectTO), 
anyTypeClasses, formLayoutInfo, pageRef);
+        super(new AnyObjectWrapper(previousAnyObjectTO, anyObjectTO),
+                anyTypeClasses, formLayoutInfo, pageRef);
     }
 
     @Override
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
index f62a8870de..07e129d470 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
@@ -30,11 +30,10 @@ import 
org.apache.syncope.client.console.layout.GroupFormLayoutInfo;
 import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.ui.commons.layout.AbstractAnyFormLayout;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
-import 
org.apache.syncope.client.ui.commons.wizards.any.AbstractAnyWizardBuilder;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizardBuilder;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyForm;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.GroupTO;
@@ -43,7 +42,7 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.extensions.wizard.WizardModel;
 
-public abstract class AnyWizardBuilder<A extends AnyTO> extends 
AbstractAnyWizardBuilder<A> {
+public abstract class AnyWizardBuilder<A extends AnyTO> extends 
AjaxWizardBuilder<AnyWrapper<A>> {
 
     private static final long serialVersionUID = -2480279868319546243L;
 
@@ -60,14 +59,6 @@ public abstract class AnyWizardBuilder<A extends AnyTO> 
extends AbstractAnyWizar
 
     protected AbstractAnyFormLayout<A, ? extends AnyForm<A>> formLayoutInfo;
 
-    /**
-     * Construct.
-     *
-     * @param anyTO any
-     * @param anyTypeClasses any type classes
-     * @param formLayoutInfo form layout info
-     * @param pageRef caller page reference.
-     */
     public AnyWizardBuilder(
             final A anyTO,
             final List<String> anyTypeClasses,
@@ -79,15 +70,7 @@ public abstract class AnyWizardBuilder<A extends AnyTO> 
extends AbstractAnyWizar
         this.formLayoutInfo = formLayoutInfo;
     }
 
-    /**
-     * Construct.
-     *
-     * @param wrapper any wrapper
-     * @param anyTypeClasses any type classes
-     * @param formLayoutInfo form layout info
-     * @param pageRef caller page reference.
-     */
-    public AnyWizardBuilder(
+    protected AnyWizardBuilder(
             final AnyWrapper<A> wrapper,
             final List<String> anyTypeClasses,
             final AbstractAnyFormLayout<A, ? extends AnyForm<A>> 
formLayoutInfo,
@@ -166,15 +149,12 @@ public abstract class AnyWizardBuilder<A extends AnyTO> 
extends AbstractAnyWizar
         }
     }
 
-    @Override
     protected void fixPlainAttrs(final AnyTO updated, final AnyTO original) {
         // re-add to the updated object any missing plain attribute (compared 
to original): this to cope with
         // form layout, which might have not included some plain attributes
-        for (Attr plainAttr : original.getPlainAttrs()) {
-            if (updated.getPlainAttr(plainAttr.getSchema()).isEmpty()) {
-                updated.getPlainAttrs().add(plainAttr);
-            }
-        }
+        original.getPlainAttrs().stream().
+                filter(plainAttr -> 
updated.getPlainAttr(plainAttr.getSchema()).isEmpty()).
+                forEach(updated.getPlainAttrs()::add);
 
         if (updated instanceof GroupableRelatableTO updatedTO && original 
instanceof GroupableRelatableTO originalTO) {
             originalTO.getMemberships().
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/ConsoleAuxClasses.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/ConsoleAuxClasses.java
index bdb8a6168f..3da0494071 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/ConsoleAuxClasses.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/ConsoleAuxClasses.java
@@ -19,14 +19,22 @@
 package org.apache.syncope.client.console.wizards.any;
 
 import java.util.List;
+import org.apache.commons.collections4.ListUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractAuxClasses;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.util.ListModel;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
-public class ConsoleAuxClasses extends AbstractAuxClasses {
+public class ConsoleAuxClasses extends WizardStep {
 
     private static final long serialVersionUID = 552437609667518888L;
 
@@ -34,11 +42,33 @@ public class ConsoleAuxClasses extends AbstractAuxClasses {
     protected AnyTypeClassRestClient anyTypeClassRestClient;
 
     public <T extends AnyTO> ConsoleAuxClasses(final AnyWrapper<T> 
modelObject, final List<String> anyTypeClasses) {
-        super(modelObject, anyTypeClasses);
-    }
+        super();
+        setOutputMarkupId(true);
+
+        List<String> choices = anyTypeClassRestClient.list().stream().
+                map(AnyTypeClassTO::getKey).filter(aux -> 
!anyTypeClasses.contains(aux)).
+                sorted().
+                toList();
+
+        add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build(
+                "auxClasses",
+                new PropertyModel<>(modelObject.getInnerObject(), 
"auxClasses"),
+                new ListModel<>(choices)).
+                hideLabel().setOutputMarkupId(true));
+
+        // ------------------
+        // insert changed label if needed
+        // ------------------
+        if (modelObject instanceof UserWrapper userWrapper
+                && userWrapper.getPreviousUserTO() != null
+                && !ListUtils.isEqualList(
+                        userWrapper.getInnerObject().getAuxClasses(),
+                        userWrapper.getPreviousUserTO().getAuxClasses())) {
 
-    @Override
-    protected List<AnyTypeClassTO> listAnyTypecClasses() {
-        return anyTypeClassRestClient.list();
+            add(new LabelInfo("changed", StringUtils.EMPTY));
+        } else {
+            add(new Label("changed", StringUtils.EMPTY));
+        }
+        // ------------------
     }
 }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
index 83995f2726..5818189441 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
@@ -50,8 +50,8 @@ public class GroupWizardBuilder extends 
AnyWizardBuilder<GroupTO> implements Gro
             final GroupRestClient groupRestClient,
             final PageReference pageRef) {
 
-        super(Optional.ofNullable(groupTO).map(GroupWrapper::new).
-                orElse(null), anyTypeClasses, formLayoutInfo, pageRef);
+        super(Optional.ofNullable(groupTO).map(GroupWrapper::new).orElse(null),
+                anyTypeClasses, formLayoutInfo, pageRef);
         this.groupRestClient = groupRestClient;
     }
 
@@ -71,7 +71,8 @@ public class GroupWizardBuilder extends 
AnyWizardBuilder<GroupTO> implements Gro
             final GroupFormLayoutInfo formLayoutInfo,
             final PageReference pageRef) {
 
-        super(new GroupWrapper(previousGroupTO, groupTO), anyTypeClasses, 
formLayoutInfo, pageRef);
+        super(new GroupWrapper(previousGroupTO, groupTO),
+                anyTypeClasses, formLayoutInfo, pageRef);
     }
 
     /**
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
index 2304d6dde4..f598cdd791 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.commons.collections4.ListUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
@@ -32,10 +33,11 @@ import 
org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroups;
 import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroupsModel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.to.AnyTO;
@@ -47,6 +49,8 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.ActionPermissions;
 import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
+import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
@@ -55,16 +59,26 @@ import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.util.ListModel;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
-public class Groups extends AbstractGroups {
+public class Groups extends WizardStep implements ICondition {
 
     private static final long serialVersionUID = 552437609667518888L;
 
+    protected static final int MAX_GROUP_LIST_CARDINALITY = 30;
+
     @SpringBean
     protected GroupRestClient groupRestClient;
 
     @SpringBean
     protected SyncopeRestClient syncopeRestClient;
 
+    protected final AnyTO anyTO;
+
+    protected WebMarkupContainer dyngroupsContainer;
+
+    protected WebMarkupContainer dynrealmsContainer;
+
+    protected WebMarkupContainer groupsContainer;
+
     protected final List<DynRealmTO> allDynRealms = new ArrayList<>();
 
     protected final boolean templateMode;
@@ -74,7 +88,31 @@ public class Groups extends AbstractGroups {
     protected AjaxPalettePanel.Builder<MembershipTO> groups;
 
     public <T extends AnyTO> Groups(final AnyWrapper<T> modelObject, final 
boolean templateMode) {
-        super(modelObject);
+        super();
+        this.anyTO = modelObject.getInnerObject();
+
+        setOutputMarkupId(true);
+
+        groupsContainer = new WebMarkupContainer("groupsContainer");
+        groupsContainer.setOutputMarkupId(true);
+        groupsContainer.setOutputMarkupPlaceholderTag(true);
+        add(groupsContainer);
+
+        // ------------------
+        // insert changed label if needed
+        // ------------------
+        if (modelObject instanceof final UserWrapper uw
+                && uw.getPreviousUserTO() != null
+                && !ListUtils.isEqualList(
+                        uw.getInnerObject().getMemberships(),
+                        uw.getPreviousUserTO().getMemberships())) {
+
+            groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY));
+        } else {
+            groupsContainer.add(new Label("changed", StringUtils.EMPTY));
+        }
+        // ------------------
+
         this.templateMode = templateMode;
         this.groupsModel = new ConsoleGroupsModel();
 
@@ -96,7 +134,6 @@ public class Groups extends AbstractGroups {
                 SyncopeConstants.ROOT_REALM, term, 1, 
Constants.MAX_GROUP_LIST_SIZE);
     }
 
-    @Override
     protected void addDynamicRealmsContainer() {
         dynrealmsContainer = new WebMarkupContainer("dynrealmsContainer");
         dynrealmsContainer.setOutputMarkupId(true);
@@ -108,7 +145,6 @@ public class Groups extends AbstractGroups {
         add(dynrealmsContainer);
     }
 
-    @Override
     protected void addGroupsPanel() {
         if (anyTO instanceof GroupTO) {
             groupsContainer.add(new Label("groups").setVisible(false));
@@ -179,7 +215,6 @@ public class Groups extends AbstractGroups {
         }
     }
 
-    @Override
     protected void addDynamicGroupsContainer() {
         dyngroupsContainer = new WebMarkupContainer("dyngroupsContainer");
         dyngroupsContainer.setOutputMarkupId(true);
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
index 4a9757b878..f5d27b0594 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
@@ -366,23 +366,24 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         }
     }
 
-    protected void fillAuxClasses(final Relatable<?, ?> any, final AnyUR 
anyUR) {
-        for (StringPatchItem patch : anyUR.getAuxClasses()) {
-            anyTypeClassDAO.findById(patch.getValue()).ifPresentOrElse(
-                    auxClass -> {
-                        switch (patch.getOperation()) {
-                            case ADD_REPLACE:
-                                any.add(auxClass);
-                                break;
-
-                            case DELETE:
-                            default:
-                                any.getAuxClasses().remove(auxClass);
-                        }
-                    },
-                    () -> LOG.debug("Invalid {} {}, ignoring...",
-                            AnyTypeClass.class.getSimpleName(), 
patch.getValue()));
-        }
+    protected void processAuxClasses(final Relatable<?, ?> any, final AnyUR 
anyUR) {
+        anyUR.getAuxClasses().forEach(patch -> 
anyTypeClassDAO.findById(patch.getValue()).ifPresentOrElse(
+                auxClass -> {
+                    switch (patch.getOperation()) {
+                        case ADD_REPLACE:
+                            any.add(auxClass);
+                            break;
+
+                        case DELETE:
+                        default:
+                            if (any.getAuxClasses().remove(auxClass)) {
+                                auxClass.getPlainSchemas().
+                                        forEach(schema -> 
any.getPlainAttr(schema.getKey()).ifPresent(any::remove));
+                            }
+                    }
+                },
+                () -> LOG.debug("Invalid {} {}, ignoring...",
+                        AnyTypeClass.class.getSimpleName(), 
patch.getValue())));
     }
 
     protected void fill(
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index baa7bbf1ad..e3d8931b3f 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -245,7 +245,7 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
 
     @Override
     public PropagationByResource<String> update(final AnyObject toBeUpdated, 
final AnyObjectUR anyObjectUR) {
-        fillAuxClasses(toBeUpdated, anyObjectUR);
+        processAuxClasses(toBeUpdated, anyObjectUR);
 
         // Re-merge any pending change from workflow tasks
         AnyObject anyObject = anyObjectDAO.save(toBeUpdated);
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index a6010aaef1..4528bd62c9 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -225,7 +225,7 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
 
     @Override
     public PropagationByResource<String> update(final Group toBeUpdated, final 
GroupUR groupUR) {
-        fillAuxClasses(toBeUpdated, groupUR);
+        processAuxClasses(toBeUpdated, groupUR);
 
         // Re-merge any pending change from workflow tasks
         Group group = groupDAO.save(toBeUpdated);
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 272459b573..63ee682f74 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -370,7 +370,7 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
 
     @Override
     public UserWorkflowResult.PropagationInfo update(final User toBeUpdated, 
final UserUR userUR) {
-        fillAuxClasses(toBeUpdated, userUR);
+        processAuxClasses(toBeUpdated, userUR);
 
         // Re-merge any pending change from workflow tasks
         User user = userDAO.save(toBeUpdated);
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
index f6eea19886..b1f5466276 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
@@ -439,4 +439,38 @@ public class AnyObjectITCase extends AbstractITCase {
         printer = updateAnyObject(printerUR).getEntity();
         checkAnyTO.accept(printer);
     }
+
+    @Test
+    public void issueSYNCOPE1960() {
+        // 1. create schema and class
+        PlainSchemaTO plainSchema = new PlainSchemaTO();
+        plainSchema.setKey("new_plain_schema" + getUUIDString());
+        plainSchema.setType(AttrSchemaType.String);
+        plainSchema = createSchema(SchemaType.PLAIN, plainSchema);
+
+        AnyTypeClassTO newClass = new AnyTypeClassTO();
+        newClass.setKey("new class" + getUUIDString());
+        newClass.getPlainSchemas().add(plainSchema.getKey());
+        ANY_TYPE_CLASS_SERVICE.create(newClass);
+
+        // 2. create printer with the new class as aux class
+        AnyObjectCR anyObjectCR = getSample("syncope1960");
+        anyObjectCR.getResources().clear();
+        anyObjectCR.getAuxClasses().add(newClass.getKey());
+        anyObjectCR.getPlainAttrs().add(attr(plainSchema.getKey(), "value"));
+
+        AnyObjectTO printer = createAnyObject(anyObjectCR).getEntity();
+        assertTrue(printer.getPlainAttr(plainSchema.getKey()).isPresent());
+        assertTrue(printer.getAuxClasses().contains(newClass.getKey()));
+
+        // 3. remove aux class
+        AnyObjectUR anyObjectUR = new AnyObjectUR.Builder(printer.getKey()).
+                auxClass(new 
StringPatchItem.Builder().value(newClass.getKey()).
+                        operation(PatchOperation.DELETE).build()).
+                build();
+
+        printer = updateAnyObject(anyObjectUR).getEntity();
+        assertTrue(printer.getPlainAttr(plainSchema.getKey()).isEmpty());
+        assertFalse(printer.getAuxClasses().contains(newClass.getKey()));
+    }
 }
diff --git a/pom.xml b/pom.xml
index a479d00cbc..0e5127a876 100644
--- a/pom.xml
+++ b/pom.xml
@@ -451,7 +451,7 @@ under the License.
     <disruptor.version>4.0.0</disruptor.version>
 
     <elasticsearch.version>9.2.7</elasticsearch.version>
-    <opensearch.version>3.5.0</opensearch.version>
+    <opensearch.version>3.6.0</opensearch.version>
     <opensearch-java.version>3.8.0</opensearch-java.version>
 
     <openfga.version>v1</openfga.version>


Reply via email to