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 893db9cb2f [SYNCOPE-1957] Process auxClasses changes before attributes 
during update
893db9cb2f is described below

commit 893db9cb2f69289d5fea658c7512067c9fccf2ae
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri Mar 27 18:26:07 2026 +0100

    [SYNCOPE-1957] Process auxClasses changes before attributes during update
---
 .../core/provisioning/java/data/AnyDataBinder.java |  20 +++-
 .../java/data/AnyObjectDataBinderImpl.java         |   2 +
 .../java/data/GroupDataBinderImpl.java             |   2 +
 .../provisioning/java/data/UserDataBinderImpl.java |   2 +
 .../core/flowable/FlowableWorkflowContext.java     |   4 +-
 .../apache/syncope/core/flowable/task/Update.java  |   8 +-
 .../apache/syncope/fit/core/AnyObjectITCase.java   | 112 +++++++++++++++++++++
 7 files changed, 140 insertions(+), 10 deletions(-)

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 dfe01d7f4f..17f63a573d 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,7 +366,25 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         }
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    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 fill(
             final AnyTO anyTO,
             final Any any,
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 ae09217b26..baa7bbf1ad 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,6 +245,8 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
 
     @Override
     public PropagationByResource<String> update(final AnyObject toBeUpdated, 
final AnyObjectUR anyObjectUR) {
+        fillAuxClasses(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 f0e5f243fc..a6010aaef1 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,6 +225,8 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
 
     @Override
     public PropagationByResource<String> update(final Group toBeUpdated, final 
GroupUR groupUR) {
+        fillAuxClasses(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 910335fb93..272459b573 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,6 +370,8 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
 
     @Override
     public UserWorkflowResult.PropagationInfo update(final User toBeUpdated, 
final UserUR userUR) {
+        fillAuxClasses(toBeUpdated, userUR);
+
         // Re-merge any pending change from workflow tasks
         User user = userDAO.save(toBeUpdated);
 
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
index b821143b37..1b1a643cfa 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
@@ -248,7 +248,7 @@ public class FlowableWorkflowContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public Update update(final UserDataBinder userDataBinder, final UserDAO 
userDAO) {
-        return new Update(userDataBinder, userDAO);
+    public Update update(final UserDataBinder userDataBinder) {
+        return new Update(userDataBinder);
     }
 }
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
index 539d12b49b..2fda104efb 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
@@ -20,7 +20,6 @@ package org.apache.syncope.core.flowable.task;
 
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
@@ -30,11 +29,8 @@ public class Update extends FlowableServiceTask {
 
     protected final UserDataBinder dataBinder;
 
-    protected final UserDAO userDAO;
-
-    public Update(final UserDataBinder dataBinder, final UserDAO userDAO) {
+    public Update(final UserDataBinder dataBinder) {
         this.dataBinder = dataBinder;
-        this.userDAO = userDAO;
     }
 
     @Override
@@ -45,8 +41,6 @@ public class Update extends FlowableServiceTask {
         } else {
             User user = execution.getVariable(FlowableRuntimeUtils.USER, 
User.class);
 
-            user = userDAO.save(user);
-
             UserWorkflowResult.PropagationInfo propInfo = 
dataBinder.update(user, req);
 
             // report updated user and propagation by resource as result
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 4b03db189c..f6eea19886 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
@@ -26,25 +26,40 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import jakarta.ws.rs.core.Response;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Consumer;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.request.AnyCR;
 import org.apache.syncope.common.lib.request.AnyObjectCR;
 import org.apache.syncope.common.lib.request.AnyObjectUR;
+import org.apache.syncope.common.lib.request.AnyUR;
+import org.apache.syncope.common.lib.request.GroupCR;
+import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.request.RelationshipUR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
+import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ConnObject;
+import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.RelationshipTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -327,4 +342,101 @@ public class AnyObjectITCase extends AbstractITCase {
         assertEquals(ClientExceptionType.InvalidRelationship, e.getType());
         assertTrue(e.getMessage().contains("Relationships shall be created or 
updated only from their left end"));
     }
+
+    public void issueSYNCOPE1957() {
+        // prepare
+        PlainSchemaTO schema1 = new PlainSchemaTO();
+        schema1.setKey("schema1" + getUUIDString());
+        schema1.setType(AttrSchemaType.Boolean);
+        createSchema(SchemaType.PLAIN, schema1);
+
+        PlainSchemaTO schema2 = new PlainSchemaTO();
+        schema2.setKey("schema2" + getUUIDString());
+        schema2.setType(AttrSchemaType.Boolean);
+        createSchema(SchemaType.PLAIN, schema2);
+
+        String class1Key = "class1" + getUUIDString();
+        AnyTypeClassTO class1 = new AnyTypeClassTO();
+        class1.setKey(class1Key);
+        class1.getPlainSchemas().add(schema1.getKey());
+        class1.getPlainSchemas().add(schema2.getKey());
+
+        Response response = ANY_TYPE_CLASS_SERVICE.create(class1);
+        assertEquals(Response.Status.CREATED.getStatusCode(), 
response.getStatusInfo().getStatusCode());
+
+        class1 = getObject(response.getLocation(), AnyTypeClassService.class, 
AnyTypeClassTO.class);
+
+        // 1. create user, group and printer with auxClass class1
+        Consumer<AnyCR> setupAnyCR = anyCR -> {
+            anyCR.getResources().clear();
+            anyCR.getAuxClasses().add(class1Key);
+            anyCR.getPlainAttrs().add(attr(schema1.getKey(), "true"));
+            anyCR.getPlainAttrs().add(attr(schema2.getKey(), "true"));
+        };
+        Consumer<AnyTO> checkAnyTO = anyTO -> {
+            assertTrue(anyTO.getPlainAttr(schema1.getKey()).isPresent());
+            assertTrue(anyTO.getPlainAttr(schema2.getKey()).isPresent());
+        };
+
+        UserCR userCR = UserITCase.getUniqueSample("[email protected]");
+        setupAnyCR.accept(userCR);
+
+        UserTO user = createUser(userCR).getEntity();
+        checkAnyTO.accept(user);
+
+        GroupCR groupCR = GroupITCase.getSample("syncope1957");
+        setupAnyCR.accept(groupCR);
+
+        GroupTO group = createGroup(groupCR).getEntity();
+        checkAnyTO.accept(group);
+
+        AnyObjectCR printerCR = getSample("syncope1957");
+        setupAnyCR.accept(printerCR);
+
+        AnyObjectTO printer = createAnyObject(printerCR).getEntity();
+        checkAnyTO.accept(printer);
+
+        // 2. create new anytypeclass and move schema there
+        class1.getPlainSchemas().remove(schema2.getKey());
+        ANY_TYPE_CLASS_SERVICE.update(class1);
+
+        class1 = ANY_TYPE_CLASS_SERVICE.read(class1.getKey());
+        assertEquals(List.of(schema1.getKey()), class1.getPlainSchemas());
+
+        String class2Key = "class2" + getUUIDString();
+        AnyTypeClassTO class2 = new AnyTypeClassTO();
+        class2.setKey(class2Key);
+        class2.getPlainSchemas().add(schema2.getKey());
+
+        response = ANY_TYPE_CLASS_SERVICE.create(class2);
+        assertEquals(Response.Status.CREATED.getStatusCode(), 
response.getStatusInfo().getStatusCode());
+
+        class2 = getObject(response.getLocation(), AnyTypeClassService.class, 
AnyTypeClassTO.class);
+        assertEquals(List.of(schema2.getKey()), class2.getPlainSchemas());
+
+        // 3. update user, group and printer by adding auxClass class2
+        Consumer<AnyUR> setupAnyUR = anyUR -> anyUR.getAuxClasses().
+                add(new StringPatchItem.Builder().value(class2Key).build());
+
+        UserUR userUR = new UserUR();
+        userUR.setKey(user.getKey());
+        setupAnyUR.accept(userUR);
+
+        user = updateUser(userUR).getEntity();
+        checkAnyTO.accept(user);
+
+        GroupUR groupUR = new GroupUR();
+        groupUR.setKey(group.getKey());
+        setupAnyUR.accept(groupUR);
+
+        group = updateGroup(groupUR).getEntity();
+        checkAnyTO.accept(group);
+
+        AnyObjectUR printerUR = new AnyObjectUR();
+        printerUR.setKey(printer.getKey());
+        setupAnyUR.accept(printerUR);
+
+        printer = updateAnyObject(printerUR).getEntity();
+        checkAnyTO.accept(printer);
+    }
 }

Reply via email to