From ec5905d1bd428d05c92c663bc7dfc0836e497a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Mon, 15 Dec 2025 08:36:29 +0100 Subject: [PATCH 1/7] [SYNCOPE-1938] Relationship attributes --- .../syncope/common/lib/AnyOperations.java | 277 +++++------- .../lib/SyncopeClientCompositeException.java | 54 +-- .../common/lib/request/RelationshipUR.java | 79 +++- .../syncope/common/lib/to/RelationshipTO.java | 72 ++- .../common/lib/to/RelationshipTypeTO.java | 20 + .../syncope/core/logic/SyncopeLogic.java | 4 +- .../persistence/api/dao/AllowedSchemas.java | 75 ++- .../core/persistence/api/dao/AnyDAO.java | 4 + .../persistence/api/dao/AnyObjectDAO.java | 2 - .../core/persistence/api/dao/GroupDAO.java | 4 +- .../api/dao/RelationshipTypeDAO.java | 4 + .../core/persistence/api/dao/UserDAO.java | 2 - .../core/persistence/api/entity/AnyUtils.java | 9 +- .../persistence/api/entity/Groupable.java | 15 +- .../persistence/api/entity/PlainAttr.java | 21 +- .../persistence/api/entity/Relatable.java | 32 +- .../api/entity/RelationshipType.java | 9 + .../api/entity/RelationshipTypeExtension.java | 26 ++ .../api/entity/{group => }/TypeExtension.java | 9 +- .../persistence/api/entity/group/Group.java | 6 +- .../api/entity/group/GroupTypeExtension.java | 28 ++ .../common/entity/AMembershipType.java | 18 + .../common/entity/DefaultAnyUtils.java | 105 ++++- .../common/entity/UMembershipType.java | 18 + .../common/validation/AnyValidator.java | 62 +-- .../persistence/jpa/PersistenceContext.java | 2 + .../jpa/dao/repo/AbstractAnyRepoExt.java | 52 ++- .../jpa/dao/repo/AnyObjectRepoExt.java | 2 - .../jpa/dao/repo/AnyObjectRepoExtImpl.java | 10 +- .../persistence/jpa/dao/repo/AnyRepoExt.java | 4 + .../jpa/dao/repo/AnyTypeClassRepoExtImpl.java | 18 +- .../jpa/dao/repo/GroupRepoExt.java | 4 +- .../jpa/dao/repo/GroupRepoExtImpl.java | 12 +- .../jpa/dao/repo/RelationshipTypeRepoExt.java | 4 + .../dao/repo/RelationshipTypeRepoExtImpl.java | 22 +- .../persistence/jpa/dao/repo/UserRepoExt.java | 2 - .../jpa/dao/repo/UserRepoExtImpl.java | 6 - .../jpa/entity/AbstractEntityFactory.java | 11 +- .../entity/AbstractGroupableRelatable.java | 18 +- .../jpa/entity/AbstractRelatable.java | 28 +- ...ension.java => AbstractTypeExtension.java} | 35 +- .../jpa/entity/JPARelationshipType.java | 28 ++ .../entity/JPARelationshipTypeExtension.java | 50 ++ .../jpa/entity/anyobject/JPAAMembership.java | 4 +- .../jpa/entity/anyobject/JPAAnyObject.java | 16 +- .../jpa/entity/group/JPAGroup.java | 29 +- .../entity/group/JPAGroupTypeExtension.java | 51 +++ .../jpa/entity/user/JPAUMembership.java | 4 +- .../persistence/jpa/entity/user/JPAUser.java | 12 +- .../core/persistence/jpa/inner/UserTest.java | 8 - .../core/persistence/jpa/outer/GroupTest.java | 4 +- .../core/persistence/jpa/outer/UserTest.java | 2 +- .../test/resources/domains/MasterContent.xml | 14 +- .../persistence/neo4j/PersistenceContext.java | 2 + .../neo4j/dao/repo/AbstractAnyRepoExt.java | 82 +++- .../neo4j/dao/repo/AnyObjectRepoExt.java | 2 - .../neo4j/dao/repo/AnyObjectRepoExtImpl.java | 9 +- .../neo4j/dao/repo/AnyRepoExt.java | 4 + .../dao/repo/AnyTypeClassRepoExtImpl.java | 18 +- .../neo4j/dao/repo/GroupRepoExt.java | 4 +- .../neo4j/dao/repo/GroupRepoExtImpl.java | 19 +- .../dao/repo/RelationshipTypeRepoExt.java | 4 + .../dao/repo/RelationshipTypeRepoExtImpl.java | 23 +- .../neo4j/dao/repo/UserRepoExt.java | 2 - .../neo4j/dao/repo/UserRepoExtImpl.java | 5 - .../entity/AbstractGroupableRelatable.java | 20 +- .../neo4j/entity/AbstractMembership.java | 27 +- .../neo4j/entity/AbstractRelatable.java | 41 +- .../neo4j/entity/AbstractRelationship.java | 56 +++ ...ension.java => AbstractTypeExtension.java} | 30 +- .../neo4j/entity/Neo4jEntityFactory.java | 11 +- .../neo4j/entity/Neo4jRelationshipType.java | 27 ++ .../Neo4jRelationshipTypeExtension.java | 48 ++ .../entity/anyobject/Neo4jAMembership.java | 26 -- .../entity/anyobject/Neo4jARelationship.java | 16 +- .../entity/anyobject/Neo4jAnyObject.java | 9 +- .../entity/group/Neo4jGRelationship.java | 16 +- .../neo4j/entity/group/Neo4jGroup.java | 30 +- .../entity/group/Neo4jGroupTypeExtension.java | 47 ++ .../neo4j/entity/user/Neo4jUMembership.java | 27 -- .../neo4j/entity/user/Neo4jURelationship.java | 16 +- .../neo4j/entity/user/Neo4jUser.java | 16 +- .../persistence/neo4j/inner/UserTest.java | 8 - .../persistence/neo4j/outer/GroupTest.java | 4 +- .../persistence/neo4j/outer/UserTest.java | 2 +- .../test/resources/domains/MasterContent.xml | 18 +- .../core/provisioning/api/DerAttrHandler.java | 22 + .../api/data/GroupDataBinder.java | 4 +- .../java/DefaultDerAttrHandler.java | 52 ++- .../java/ProvisioningContext.java | 5 +- .../provisioning/java/data/AnyDataBinder.java | 427 ++++++++++++++---- .../java/data/AnyObjectDataBinderImpl.java | 133 +----- .../java/data/GroupDataBinderImpl.java | 30 +- .../data/RelationshipTypeDataBinderImpl.java | 65 ++- .../java/data/UserDataBinderImpl.java | 124 +---- .../DefaultNotificationManager.java | 26 +- .../java/data/ResourceDataBinderTest.java | 4 +- .../java/data/UserDataBinderTest.java | 74 ++- .../core/flowable/task/AutoActivate.java | 12 +- .../flowable/CreateARelationship.java | 5 +- .../syncope/fit/core/AnyObjectITCase.java | 100 ---- .../syncope/fit/core/MembershipITCase.java | 13 +- .../syncope/fit/core/RelationshipITCase.java | 171 +++++++ 103 files changed, 2229 insertions(+), 1114 deletions(-) create mode 100644 core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipTypeExtension.java rename core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/{group => }/TypeExtension.java (77%) create mode 100644 core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/GroupTypeExtension.java rename core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/{group/JPATypeExtension.java => AbstractTypeExtension.java} (66%) create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java create mode 100644 core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java rename core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/{group/Neo4jTypeExtension.java => AbstractTypeExtension.java} (63%) create mode 100644 core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipTypeExtension.java create mode 100644 core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroupTypeExtension.java create mode 100644 fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java index 9ea7ca0b857..fafebf7952a 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java @@ -44,8 +44,10 @@ import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.LinkedAccountTO; import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelatableTO; import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.PatchOperation; @@ -150,6 +152,70 @@ private static void diff( operation(PatchOperation.ADD_REPLACE).value(resource).build())); } + private static void relationships( + final RelatableTO updated, + final RelatableTO original, + final boolean incremental, + final Set updateReqs) { + + Map, RelationshipTO> updatedRels = + EntityTOUtils.buildRelationshipMap(updated.getRelationships()); + Map, RelationshipTO> originalRels = + EntityTOUtils.buildRelationshipMap(original.getRelationships()); + + updatedRels.forEach((pair, relationship) -> { + if (!originalRels.containsKey(pair) + || (originalRels.containsKey(pair) && !originalRels.get(pair).equals(relationship))) { + + RelationshipUR patch = new RelationshipUR.Builder(relationship.getType()). + otherEnd(relationship.getOtherEndType(), relationship.getOtherEndKey()). + operation(PatchOperation.ADD_REPLACE).build(); + + patch.getPlainAttrs().addAll(relationship.getPlainAttrs().stream(). + filter(attr -> !isEmpty(attr)).toList()); + + updateReqs.add(patch); + } + }); + + if (!incremental) { + originalRels.keySet().stream().filter(pair -> !updatedRels.containsKey(pair)). + forEach(pair -> updateReqs.add( + new RelationshipUR.Builder(originalRels.get(pair).getType()). + otherEnd(originalRels.get(pair).getOtherEndType(), originalRels.get(pair).getOtherEndKey()). + operation(PatchOperation.DELETE).build())); + } + } + + private static void memberships( + final GroupableRelatableTO updated, + final GroupableRelatableTO original, + final boolean incremental, + final Set updateReqs) { + + Map updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships()); + Map originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships()); + + updatedMembs.forEach((group, membership) -> { + if (!originalMembs.containsKey(group) + || (originalMembs.containsKey(group) && !originalMembs.get(group).equals(membership))) { + + MembershipUR patch = new MembershipUR.Builder(group).operation(PatchOperation.ADD_REPLACE).build(); + + patch.getPlainAttrs().addAll(membership.getPlainAttrs().stream(). + filter(attr -> !isEmpty(attr)).toList()); + + updateReqs.add(patch); + } + }); + + if (!incremental) { + originalMembs.keySet().stream().filter(group -> !updatedMembs.containsKey(group)). + forEach(group -> updateReqs.add( + new MembershipUR.Builder(group).operation(PatchOperation.DELETE).build())); + } + } + /** * Calculate modifications needed by first in order to be equal to second. * @@ -169,54 +235,14 @@ public static AnyObjectUR diff( result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem())); // 2. relationships - Map, RelationshipTO> updatedRels = - EntityTOUtils.buildRelationshipMap(updated.getRelationships()); - Map, RelationshipTO> originalRels = - EntityTOUtils.buildRelationshipMap(original.getRelationships()); - - updatedRels.entrySet().stream(). - filter(entry -> !originalRels.containsKey(entry.getKey())). - forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()). - operation(PatchOperation.ADD_REPLACE).build())); - - if (!incremental) { - originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)). - forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)). - operation(PatchOperation.DELETE).build())); - } + relationships(updated, original, incremental, result.getRelationships()); // 3. memberships - Map updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships()); - Map originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships()); - - updatedMembs.forEach((key, value) -> { - MembershipUR membershipPatch = new MembershipUR.Builder(value.getGroupKey()). - operation(PatchOperation.ADD_REPLACE).build(); - - diff(value, membershipPatch); - result.getMemberships().add(membershipPatch); - }); - - if (!incremental) { - originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)). - forEach(key -> result.getMemberships().add( - new MembershipUR.Builder(originalMembs.get(key).getGroupKey()). - operation(PatchOperation.DELETE).build())); - } + memberships(updated, original, incremental, result.getMemberships()); return result; } - private static void diff( - final MembershipTO updated, - final MembershipUR result) { - - // plain attributes - result.getPlainAttrs().addAll(updated.getPlainAttrs().stream(). - filter(attr -> !isEmpty(attr)). - collect(Collectors.toSet())); - } - /** * Calculate modifications needed by first in order to be equal to second. * @@ -271,40 +297,10 @@ public static UserUR diff(final UserTO updated, final UserTO original, final boo operation(PatchOperation.ADD_REPLACE).value(toAdd).build())); // 5. relationships - Map, RelationshipTO> updatedRels = - EntityTOUtils.buildRelationshipMap(updated.getRelationships()); - Map, RelationshipTO> originalRels = - EntityTOUtils.buildRelationshipMap(original.getRelationships()); - - updatedRels.entrySet().stream(). - filter(entry -> !originalRels.containsKey(entry.getKey())). - forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()). - operation(PatchOperation.ADD_REPLACE).build())); - - if (!incremental) { - originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)). - forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)). - operation(PatchOperation.DELETE).build())); - } + relationships(updated, original, incremental, result.getRelationships()); // 6. memberships - Map updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships()); - Map originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships()); - - updatedMembs.forEach((key, value) -> { - MembershipUR membershipPatch = new MembershipUR.Builder(value.getGroupKey()). - operation(PatchOperation.ADD_REPLACE).build(); - - diff(value, membershipPatch); - result.getMemberships().add(membershipPatch); - }); - - if (!incremental) { - originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)) - .forEach(key -> result.getMemberships() - .add(new MembershipUR.Builder(originalMembs.get(key).getGroupKey()) - .operation(PatchOperation.DELETE).build())); - } + memberships(updated, original, incremental, result.getMemberships()); // 7. linked accounts Map, LinkedAccountTO> updatedAccounts = @@ -312,18 +308,15 @@ public static UserUR diff(final UserTO updated, final UserTO original, final boo Map, LinkedAccountTO> originalAccounts = EntityTOUtils.buildLinkedAccountMap(original.getLinkedAccounts()); - updatedAccounts.forEach((key, value) - -> result.getLinkedAccounts().add(new LinkedAccountUR.Builder(). - operation(PatchOperation.ADD_REPLACE). - linkedAccountTO(value).build())); + updatedAccounts.forEach((key, value) -> result.getLinkedAccounts().add(new LinkedAccountUR.Builder(). + operation(PatchOperation.ADD_REPLACE). + linkedAccountTO(value).build())); if (!incremental) { originalAccounts.keySet().stream().filter(account -> !updatedAccounts.containsKey(account)). - forEach(key -> { - result.getLinkedAccounts().add(new LinkedAccountUR.Builder(). - operation(PatchOperation.DELETE). - linkedAccountTO(originalAccounts.get(key)).build()); - }); + forEach(key -> result.getLinkedAccounts().add(new LinkedAccountUR.Builder(). + operation(PatchOperation.DELETE). + linkedAccountTO(originalAccounts.get(key)).build())); } return result; @@ -359,21 +352,7 @@ public static GroupUR diff(final GroupTO updated, final GroupTO original, final result.getTypeExtensions().addAll(updated.getTypeExtensions()); // 5. relationships - Map, RelationshipTO> updatedRels = - EntityTOUtils.buildRelationshipMap(updated.getRelationships()); - Map, RelationshipTO> originalRels = - EntityTOUtils.buildRelationshipMap(original.getRelationships()); - - updatedRels.entrySet().stream(). - filter(entry -> !originalRels.containsKey(entry.getKey())). - forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()). - operation(PatchOperation.ADD_REPLACE).build())); - - if (!incremental) { - originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)). - forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)). - operation(PatchOperation.DELETE).build())); - } + relationships(updated, original, incremental, result.getRelationships()); return result; } @@ -468,6 +447,30 @@ public static AnyTO patch(final AnyTO anyTO, final AnyUR anyUR) { return null; } + private static void relationships(final Set updateReqs, final RelatableTO relatable) { + updateReqs.forEach(relPatch -> { + if (relPatch.getType() == null || relPatch.getOtherEndType() == null || relPatch.getOtherEndKey() == null) { + LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch); + } else { + relatable.getRelationships().stream(). + filter(relationship -> relPatch.getType().equals(relationship.getType()) + && relPatch.getOtherEndType().equals(relationship.getOtherEndType()) + && relPatch.getOtherEndKey().equals(relationship.getOtherEndKey())). + findFirst().ifPresent(memb -> relatable.getRelationships().remove(memb)); + + if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) { + RelationshipTO newRelationshipTO = new RelationshipTO.Builder(relPatch.getType()). + otherEnd(relPatch.getOtherEndType(), relPatch.getOtherEndKey()). + // 3. plain attributes + plainAttrs(relPatch.getPlainAttrs()). + build(); + + relatable.getRelationships().add(newRelationshipTO); + } + } + }); + } + public static GroupTO patch(final GroupTO groupTO, final GroupUR groupUR) { GroupTO result = SerializationUtils.clone(groupTO); patch(groupTO, groupUR, result); @@ -487,38 +490,19 @@ public static GroupTO patch(final GroupTO groupTO, final GroupUR groupUR) { result.getADynMembershipConds().clear(); result.getADynMembershipConds().putAll(groupUR.getADynMembershipConds()); + relationships(groupUR.getRelationships(), result); + return result; } - public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectUR anyObjectUR) { - AnyObjectTO result = SerializationUtils.clone(anyObjectTO); - patch(anyObjectTO, anyObjectUR, result); - - if (anyObjectUR.getName() != null) { - result.setName(anyObjectUR.getName().getValue()); - } - - // 1. relationships - anyObjectUR.getRelationships().forEach(relPatch -> { - if (relPatch.getRelationshipTO() == null) { - LOG.warn("Invalid {} specified, no {} provided", - RelationshipUR.class.getName(), RelationshipTO.class.getName()); - } else { - result.getRelationships().remove(relPatch.getRelationshipTO()); - if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) { - result.getRelationships().add(relPatch.getRelationshipTO()); - } - } - }); - - // 2. memberships - anyObjectUR.getMemberships().forEach(membPatch -> { + private static void memberships(final Set updateReqs, final GroupableRelatableTO groupable) { + updateReqs.forEach(membPatch -> { if (membPatch.getGroup() == null) { LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch); } else { - result.getMemberships().stream(). + groupable.getMemberships().stream(). filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())). - findFirst().ifPresent(memb -> result.getMemberships().remove(memb)); + findFirst().ifPresent(memb -> groupable.getMemberships().remove(memb)); if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) { MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()). @@ -526,10 +510,25 @@ public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectUR plainAttrs(membPatch.getPlainAttrs()). build(); - result.getMemberships().add(newMembershipTO); + groupable.getMemberships().add(newMembershipTO); } } }); + } + + public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectUR anyObjectUR) { + AnyObjectTO result = SerializationUtils.clone(anyObjectTO); + patch(anyObjectTO, anyObjectUR, result); + + if (anyObjectUR.getName() != null) { + result.setName(anyObjectUR.getName().getValue()); + } + + // 1. relationships + relationships(anyObjectUR.getRelationships(), result); + + // 2. memberships + memberships(anyObjectUR.getMemberships(), result); return result; } @@ -549,36 +548,10 @@ public static UserTO patch(final UserTO userTO, final UserUR userUR) { } // 3. relationships - userUR.getRelationships().forEach(relPatch -> { - if (relPatch.getRelationshipTO() == null) { - LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch); - } else { - result.getRelationships().remove(relPatch.getRelationshipTO()); - if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) { - result.getRelationships().add(relPatch.getRelationshipTO()); - } - } - }); + relationships(userUR.getRelationships(), result); // 4. memberships - userUR.getMemberships().forEach(membPatch -> { - if (membPatch.getGroup() == null) { - LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch); - } else { - result.getMemberships().stream(). - filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())). - findFirst().ifPresent(memb -> result.getMemberships().remove(memb)); - - if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) { - MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()). - // 3. plain attributes - plainAttrs(membPatch.getPlainAttrs()). - build(); - - result.getMemberships().add(newMembershipTO); - } - } - }); + memberships(userUR.getMemberships(), result); // 5. roles for (StringPatchItem rolePatch : userUR.getRoles()) { diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java index 368b97c3e55..a78b7d18a79 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java @@ -19,9 +19,9 @@ package org.apache.syncope.common.lib; import java.util.HashSet; -import java.util.Iterator; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.apache.syncope.common.lib.types.ClientExceptionType; public class SyncopeClientCompositeException extends SyncopeClientException { @@ -38,23 +38,8 @@ public boolean hasExceptions() { return !exceptions.isEmpty(); } - public boolean hasException(final ClientExceptionType exceptionType) { - return getException(exceptionType) != null; - } - - public SyncopeClientException getException(final ClientExceptionType exceptionType) { - boolean found = false; - SyncopeClientException syncopeClientException = null; - for (Iterator itor = exceptions.iterator(); itor.hasNext() && !found;) { - syncopeClientException = itor.next(); - if (syncopeClientException.getType().equals(exceptionType)) { - found = true; - } - } - - return found - ? syncopeClientException - : null; + public Optional getException(final ClientExceptionType exceptionType) { + return exceptions.stream().filter(e -> e.getType() == exceptionType).findFirst(); } public Set getExceptions() { @@ -63,35 +48,22 @@ public Set getExceptions() { public boolean addException(final SyncopeClientException exception) { if (exception.getType() == null) { - throw new IllegalArgumentException(exception + " does not have the right " - + ClientExceptionType.class.getName() + " set"); + exception.setType(ClientExceptionType.Unknown); } - Optional alreadyAdded = - exceptions.stream().filter(ex -> ex.getType() == exception.getType()).findFirst(); - - return alreadyAdded.map(e -> e.getElements() - .addAll(exception.getElements())).orElseGet(() -> exceptions.add(exception)); + return exceptions.stream(). + filter(e -> e.getType() == exception.getType()).findFirst(). + map(e -> e.getElements().addAll(exception.getElements())). + orElseGet(() -> exceptions.add(exception)); } @Override public String getMessage() { - StringBuilder message = new StringBuilder(); - - message.append('{'); - Iterator iter = getExceptions().iterator(); - while (iter.hasNext()) { - SyncopeClientException e = iter.next(); - message.append('['). - append(e.getMessage()). - append(']'); - if (iter.hasNext()) { - message.append(", "); - } - } - message.append('}'); - - return message.toString(); + return new StringBuilder(). + append('{'). + append(getExceptions().stream().map(e -> '[' + e.getMessage() + ']').collect(Collectors.joining(", "))). + append('}'). + toString(); } @Override diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java index 45da77ad5f9..d06a712a3ea 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java @@ -18,9 +18,15 @@ */ package org.apache.syncope.common.lib.request; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.syncope.common.lib.to.RelationshipTO; +import org.apache.syncope.common.lib.Attr; public class RelationshipUR extends AbstractPatch { @@ -28,32 +34,83 @@ public class RelationshipUR extends AbstractPatch { public static class Builder extends AbstractPatch.Builder { - public Builder(final RelationshipTO relationshipTO) { + public Builder(final String type) { super(); - getInstance().setRelationshipTO(relationshipTO); + getInstance().setType(type); } @Override protected RelationshipUR newInstance() { return new RelationshipUR(); } + + public Builder otherEnd(final String type, final String key) { + getInstance().setOtherEndType(type); + getInstance().setOtherEndKey(key); + return this; + } + + public Builder plainAttr(final Attr plainAttr) { + getInstance().getPlainAttrs().add(plainAttr); + return this; + } + + public Builder plainAttrs(final Attr... plainAttrs) { + getInstance().getPlainAttrs().addAll(List.of(plainAttrs)); + return this; + } + + public Builder plainAttrs(final Collection plainAttrs) { + getInstance().getPlainAttrs().addAll(plainAttrs); + return this; + } } - private RelationshipTO relationshipTO; + private String type; + + private String otherEndType; + + private String otherEndKey; + + private final Set plainAttrs = new HashSet<>(); + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public String getOtherEndType() { + return otherEndType; + } + + public void setOtherEndType(final String otherEndType) { + this.otherEndType = otherEndType; + } + + public String getOtherEndKey() { + return otherEndKey; + } - public RelationshipTO getRelationshipTO() { - return relationshipTO; + public void setOtherEndKey(final String otherEndKey) { + this.otherEndKey = otherEndKey; } - public void setRelationshipTO(final RelationshipTO relationshipTO) { - this.relationshipTO = relationshipTO; + @JacksonXmlElementWrapper(localName = "plainAttrs") + @JacksonXmlProperty(localName = "plainAttr") + public Set getPlainAttrs() { + return plainAttrs; } @Override public int hashCode() { return new HashCodeBuilder(). appendSuper(super.hashCode()). - append(relationshipTO). + append(type). + append(otherEndType). + append(otherEndKey). build(); } @@ -71,7 +128,9 @@ public boolean equals(final Object obj) { final RelationshipUR other = (RelationshipUR) obj; return new EqualsBuilder(). appendSuper(super.equals(obj)). - append(relationshipTO, other.relationshipTO). + append(type, other.type). + append(otherEndType, other.otherEndType). + append(otherEndKey, other.otherEndKey). build(); } } diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java index 4222a246114..58645071cf0 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java @@ -18,11 +18,22 @@ */ package org.apache.syncope.common.lib.to; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.BaseBean; -public class RelationshipTO implements BaseBean { +public class RelationshipTO implements BaseBean, AttributableTO { private static final long serialVersionUID = 360672942026613929L; @@ -59,6 +70,21 @@ public Builder otherEnd(final String otherEndType, final String otherEndKey, fin return this; } + public Builder plainAttr(final Attr plainAttr) { + instance.getPlainAttrs().add(plainAttr); + return this; + } + + public Builder plainAttrs(final Attr... plainAttrs) { + instance.getPlainAttrs().addAll(List.of(plainAttrs)); + return this; + } + + public Builder plainAttrs(final Collection plainAttrs) { + instance.getPlainAttrs().addAll(plainAttrs); + return this; + } + public RelationshipTO build() { return instance; } @@ -74,6 +100,10 @@ public RelationshipTO build() { private String otherEndName; + private final Set plainAttrs = new TreeSet<>(); + + private final Set derAttrs = new TreeSet<>(); + public String getType() { return type; } @@ -114,6 +144,32 @@ public void setEnd(final End end) { this.end = end; } + @JacksonXmlElementWrapper(localName = "plainAttrs") + @JacksonXmlProperty(localName = "plainAttr") + @Override + public Set getPlainAttrs() { + return plainAttrs; + } + + @JsonIgnore + @Override + public Optional getPlainAttr(final String schema) { + return plainAttrs.stream().filter(attr -> attr.getSchema().equals(schema)).findFirst(); + } + + @JacksonXmlElementWrapper(localName = "derAttrs") + @JacksonXmlProperty(localName = "derAttr") + @Override + public Set getDerAttrs() { + return derAttrs; + } + + @JsonIgnore + @Override + public Optional getDerAttr(final String schema) { + return derAttrs.stream().filter(attr -> attr.getSchema().equals(schema)).findFirst(); + } + @Override public int hashCode() { return new HashCodeBuilder(). @@ -122,6 +178,8 @@ public int hashCode() { append(otherEndKey). append(otherEndName). append(end). + append(plainAttrs). + append(derAttrs). build(); } @@ -143,6 +201,18 @@ public boolean equals(final Object obj) { append(otherEndKey, other.otherEndKey). append(otherEndName, other.otherEndName). append(end, other.end). + append(plainAttrs, other.plainAttrs). + append(derAttrs, other.derAttrs). + build(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE). + append(type). + append(end). + append(otherEndType). + append(otherEndKey). build(); } } diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java index e0a42bcb0f0..87829232b63 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java @@ -18,7 +18,13 @@ */ package org.apache.syncope.common.lib.to; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import jakarta.ws.rs.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; public class RelationshipTypeTO implements EntityTO { @@ -32,6 +38,8 @@ public class RelationshipTypeTO implements EntityTO { private String rightEndAnyType; + private final List typeExtensions = new ArrayList<>(); + @Override public String getKey() { return key; @@ -66,4 +74,16 @@ public String getRightEndAnyType() { public void setRightEndAnyType(final String rightEndAnyType) { this.rightEndAnyType = rightEndAnyType; } + + @JsonIgnore + public Optional getTypeExtension(final String anyType) { + return typeExtensions.stream().filter( + typeExtension -> anyType != null && anyType.equals(typeExtension.getAnyType())).findFirst(); + } + + @JacksonXmlElementWrapper(localName = "typeExtensions") + @JacksonXmlProperty(localName = "typeExtension") + public List getTypeExtensions() { + return typeExtensions; + } } diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java index bc3896c56ba..f9c85e23352 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java @@ -40,7 +40,7 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.search.SyncopePage; import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; import org.apache.syncope.core.spring.security.AuthContextUtils; @@ -136,7 +136,7 @@ public TypeExtensionTO readTypeExtension(final String groupName) { Group group = groupDAO.findByName(groupName). orElseThrow(() -> new NotFoundException("Group " + groupName)); - TypeExtension typeExt = group.getTypeExtension(anyTypeDAO.getUser()). + GroupTypeExtension typeExt = group.getTypeExtension(anyTypeDAO.getUser()). orElseThrow(() -> new NotFoundException("TypeExtension in " + groupName + " for users")); return groupDataBinder.getTypeExtensionTO(typeExt); diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AllowedSchemas.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AllowedSchemas.java index 0e1bd9b08f1..819708bcf6c 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AllowedSchemas.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AllowedSchemas.java @@ -21,82 +21,61 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.Schema; import org.apache.syncope.core.persistence.api.entity.group.Group; public class AllowedSchemas { - private final Set forSelf = new HashSet<>(); + private final Set self = new HashSet<>(); - private final Map> forMemberships = new HashMap<>(); + private final Map> memberships = new HashMap<>(); - public Set getForSelf() { - return forSelf; - } + private final Map> relationshipTypes = new HashMap<>(); - public Set getForMembership(final Group group) { - return forMemberships.get(group) == null ? Set.of() : forMemberships.get(group); + public Set self() { + return self; } - public Map> getForMemberships() { - return forMemberships; + public Set membership(final Group group) { + return Optional.ofNullable(memberships.get(group)).orElseGet(Set::of); } - public boolean forSelfContains(final S schema) { - return forSelf.contains(schema); + public Map> memberships() { + return memberships; } - public boolean forSelfContains(final String schema) { - return forSelf.stream().anyMatch(new KeyMatches(schema)); + public Set relationshipType(final RelationshipType relationshipType) { + return Optional.ofNullable(relationshipTypes.get(relationshipType)).orElseGet(Set::of); } - public boolean forMembershipsContains(final Group group, final S schema) { - return getForMembership(group).stream().anyMatch(s -> s.equals(schema)); + public Map> relationshipTypes() { + return relationshipTypes; } - public boolean forMembershipsContains(final S schema) { - return forMemberships.entrySet().stream(). - anyMatch(entry -> entry.getValue().contains(schema)); + public boolean selfContains(final S schema) { + return self.contains(schema); } - public boolean forMembershipsContains(final Group group, final String schema) { - return getForMembership(group).stream().anyMatch(new KeyMatches(schema)); + public boolean selfContains(final String schema) { + return self.stream().anyMatch(s -> s.getKey().equals(schema)); } - public boolean forMembershipsContains(final String schema) { - KeyMatches keyMatches = new KeyMatches(schema); - - return forMemberships.entrySet().stream(). - anyMatch(entry -> entry.getValue().stream().anyMatch(keyMatches)); + public boolean membershipsContains(final Group group, final S schema) { + return membership(group).contains(schema); } - public boolean contains(final S schema) { - if (forSelfContains(schema)) { - return true; - } - return forMembershipsContains(schema); + public boolean membershipsContains(final Group group, final String schema) { + return membership(group).stream().anyMatch(s -> s.getKey().equals(schema)); } - public boolean contains(final String schema) { - if (forSelfContains(schema)) { - return true; - } - return forMembershipsContains(schema); + public boolean relationshipTypesContains(final RelationshipType relationshipType, final S schema) { + return relationshipType(relationshipType).contains(schema); } - private class KeyMatches implements Predicate { - - private final String schema; - - KeyMatches(final String schema) { - this.schema = schema; - } - - @Override - public boolean test(final S object) { - return object.getKey().equals(schema); - } + public boolean relationshipTypesContains(final RelationshipType relationshipType, final String schema) { + return relationshipType(relationshipType).stream().anyMatch(s -> s.getKey().equals(schema)); } } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java index 389943c992c..038c318c7d6 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java @@ -27,7 +27,9 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond; import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.Schema; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -72,4 +74,6 @@ default SearchCond getAllMatchingCond() { List findDynRealms(String key); Collection findAllResourceKeys(String key); + + void deleteRelationship(Relationship relationship); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java index 782e3927364..f8552ad8451 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java @@ -60,8 +60,6 @@ public interface AnyObjectDAO extends AnyDAO { Map countByRealm(AnyType anyType); - AMembership findMembership(String key); - void deleteMembership(AMembership membership); List findDynGroups(String key); diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java index 54e93161968..4970ba7b8d5 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java @@ -27,7 +27,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.springframework.data.domain.Pageable; @@ -131,5 +131,5 @@ record DynMembershipInfo(Set before, Set after) { */ Group saveAndRefreshDynMemberships(Group group); - List findTypeExtensions(AnyTypeClass anyTypeClass); + List findTypeExtensions(AnyTypeClass anyTypeClass); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RelationshipTypeDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RelationshipTypeDAO.java index 602d2932462..4c8eb935363 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RelationshipTypeDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RelationshipTypeDAO.java @@ -20,11 +20,15 @@ import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public interface RelationshipTypeDAO extends DAO { List findByEndAnyType(AnyType anyType); List findByLeftEndAnyType(AnyType anyType); + + List findTypeExtensions(AnyTypeClass anyTypeClass); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java index 9e0eb80555a..540c72f80fb 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java @@ -58,8 +58,6 @@ public interface UserDAO extends AnyDAO { List findBySecurityQuestion(SecurityQuestion securityQuestion); - UMembership findMembership(String key); - void deleteMembership(UMembership membership); List findDynRoles(String key); diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java index 48350b0e396..a188b5ef923 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java @@ -28,6 +28,7 @@ import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager; import org.apache.syncope.core.persistence.api.dao.AnyDAO; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.group.Group; public interface AnyUtils { @@ -51,7 +52,11 @@ public interface AnyUtils { void removeAttr(String key, PlainSchema schema); - void addRelationship(Relatable relatable, RelationshipType relationshipType, AnyObject otherEnd); + Membership add(Groupable groupable, Group group); - void removeRelationship(Relatable relatable, RelationshipType relationshipType, String otherEndKey); + void remove(Groupable groupable, Membership membership); + + Relationship add(Relatable relatable, RelationshipType relationshipType, AnyObject otherEnd); + + void remove(Relatable relatable, Relationship relationship); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java index c8f1ca01ba7..2505fbbb225 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.core.persistence.api.entity; -import java.util.Collection; import java.util.List; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; @@ -27,8 +26,7 @@ public interface Groupable, REL extends R extends Any { /** - * Returns the plain attribute for this instance, the given schema name and the given membership - - * if found, {@code NULL} otherwise. + * Returns the plain attribute for this instance, the given schema name and the given membership. * * @param plainSchema plain schema name * @param membership membership @@ -36,22 +34,13 @@ public interface Groupable, REL extends R */ Optional getPlainAttr(String plainSchema, Membership membership); - /** - * Returns the list of plain attributes for this instance and the given schema name (including membeship attributes, - * as opposite to {@link Any#getPlainAttr(java.lang.String)}). - * - * @param plainSchema plain schema name - * @return list of plain attributes for this instance and the given schema name (including membeship attributes) - */ - Collection getPlainAttrs(String plainSchema); - /** * Returns the list of plain attributes for this instance and the given membership. * * @param membership membership * @return list of plain attributes for this instance and the given membership */ - Collection getPlainAttrs(Membership membership); + List getPlainAttrs(Membership membership); boolean add(M membership); diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java index 2c442f1bd85..cc5741b386e 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java @@ -59,6 +59,11 @@ public class PlainAttr implements Serializable { */ private String membership; + /** + * The relationship of this attribute; might be {@code NULL} if this attribute is not related to a relationship. + */ + private String relationship; + public String getSchema() { return schema; } @@ -141,8 +146,20 @@ public void setMembership(final String membership) { this.membership = membership; } + public String getRelationship() { + return relationship; + } + + public void setRelationship(final String relationship) { + this.relationship = relationship; + } + @JsonIgnore public boolean isValid() { + if (membership != null && relationship != null) { + return false; + } + boolean validSchema = false; try { validSchema = fetchPlainSchema() != null; @@ -159,6 +176,7 @@ public int hashCode() { append(values). append(uniqueValue). append(membership). + append(relationship). build(); } @@ -173,13 +191,13 @@ public boolean equals(final Object obj) { if (getClass() != obj.getClass()) { return false; } - @SuppressWarnings("unchecked") final PlainAttr other = (PlainAttr) obj; return new EqualsBuilder(). append(schema, other.schema). append(values, other.values). append(uniqueValue, other.uniqueValue). append(membership, other.membership). + append(relationship, other.relationship). build(); } @@ -190,6 +208,7 @@ public String toString() { append(values). append(uniqueValue). append(membership). + append(relationship). build(); } } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java index d1d352db371..6a58d0d9494 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java @@ -18,20 +18,38 @@ */ package org.apache.syncope.core.persistence.api.entity; -import java.util.Collection; import java.util.List; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; -public interface Relatable> extends Any { +public interface Relatable> extends Any { - boolean add(REL relationship); + /** + * Returns the plain attribute for this instance, the given schema name and the given relationship. + * + * @param plainSchema plain schema name + * @param relationship relationship + * @return plain attribute for this instance, the given schema name and the given membership + */ + Optional getPlainAttr(String plainSchema, Relationship relationship); - Optional getRelationship(RelationshipType relationshipType, String otherEndKey); + /** + * Returns the list of plain attributes for this instance and the given membership. + * + * @param relationship membership + * @return list of plain attributes for this instance and the given membership + */ + List getPlainAttrs(Relationship relationship); - Collection getRelationships(String otherEndKey); + boolean add(R relationship); - Collection getRelationships(RelationshipType relationshipType); + boolean remove(Relationship relationship); - List getRelationships(); + Optional getRelationship(RelationshipType relationshipType, String otherEndKey); + + List getRelationships(String otherEndKey); + + List getRelationships(RelationshipType relationshipType); + + List getRelationships(); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipType.java index e8a6f1b5189..7772175f21f 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipType.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipType.java @@ -18,6 +18,9 @@ */ package org.apache.syncope.core.persistence.api.entity; +import java.util.List; +import java.util.Optional; + public interface RelationshipType extends ProvidedKeyEntity { String getDescription(); @@ -31,4 +34,10 @@ public interface RelationshipType extends ProvidedKeyEntity { AnyType getRightEndAnyType(); void setRightEndAnyType(AnyType anyType); + + boolean add(RelationshipTypeExtension typeExtension); + + Optional getTypeExtension(AnyType anyType); + + List getTypeExtensions(); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipTypeExtension.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipTypeExtension.java new file mode 100644 index 00000000000..d062c4f6bd9 --- /dev/null +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/RelationshipTypeExtension.java @@ -0,0 +1,26 @@ +/* + * 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.core.persistence.api.entity; + +public interface RelationshipTypeExtension extends TypeExtension { + + RelationshipType getRelationshipType(); + + void setRelationshipType(RelationshipType relationshipType); +} diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/TypeExtension.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/TypeExtension.java similarity index 77% rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/TypeExtension.java rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/TypeExtension.java index effc2f56a5e..c67c6db8d79 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/TypeExtension.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/TypeExtension.java @@ -16,19 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.core.persistence.api.entity.group; +package org.apache.syncope.core.persistence.api.entity; import java.util.List; -import org.apache.syncope.core.persistence.api.entity.AnyType; -import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; -import org.apache.syncope.core.persistence.api.entity.Entity; public interface TypeExtension extends Entity { - Group getGroup(); - - void setGroup(Group group); - AnyType getAnyType(); void setAnyType(AnyType anyType); diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java index 8911bbd51e6..d2a9a79ef5c 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java @@ -50,9 +50,9 @@ public interface Group extends Relatable { List getADynMemberships(); - boolean add(TypeExtension typeExtension); + boolean add(GroupTypeExtension typeExtension); - Optional getTypeExtension(AnyType anyType); + Optional getTypeExtension(AnyType anyType); - List getTypeExtensions(); + List getTypeExtensions(); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/GroupTypeExtension.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/GroupTypeExtension.java new file mode 100644 index 00000000000..172cc87cf68 --- /dev/null +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/GroupTypeExtension.java @@ -0,0 +1,28 @@ +/* + * 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.core.persistence.api.entity.group; + +import org.apache.syncope.core.persistence.api.entity.TypeExtension; + +public interface GroupTypeExtension extends TypeExtension { + + Group getGroup(); + + void setGroup(Group group); +} diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/AMembershipType.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/AMembershipType.java index e370db087f4..4deee96bfdb 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/AMembershipType.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/AMembershipType.java @@ -18,10 +18,13 @@ */ package org.apache.syncope.core.persistence.common.entity; +import java.util.List; +import java.util.Optional; import org.apache.syncope.core.persistence.api.ApplicationContextProvider; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public class AMembershipType implements RelationshipType { @@ -74,4 +77,19 @@ public AnyType getRightEndAnyType() { public void setRightEndAnyType(final AnyType anyType) { // cannot be changed } + + @Override + public boolean add(final RelationshipTypeExtension typeExtension) { + return false; + } + + @Override + public Optional getTypeExtension(final AnyType anyType) { + return Optional.empty(); + } + + @Override + public List getTypeExtensions() { + return List.of(); + } } diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java index eea55305346..875dfc10b3c 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java @@ -54,14 +54,19 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtils; import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.Groupable; +import org.apache.syncope.core.persistence.api.entity.Membership; import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Relatable; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.api.entity.user.User; import org.slf4j.Logger; @@ -345,11 +350,75 @@ public void removeAttr(final String key, final PlainSchema schema) { @Transactional @Override - public void addRelationship( + public Membership add( + final Groupable groupable, + final Group group) { + + Membership result = null; + + switch (anyTypeKind) { + case USER -> { + UMembership umembership = entityFactory.newEntity(UMembership.class); + umembership.setRightEnd(group); + umembership.setLeftEnd((User) groupable); + + ((User) groupable).add(umembership); + + result = umembership; + } + + case ANY_OBJECT -> { + AMembership amembership = entityFactory.newEntity(AMembership.class); + amembership.setRightEnd(group); + amembership.setLeftEnd((AnyObject) groupable); + + ((AnyObject) groupable).add(amembership); + + result = amembership; + } + + default -> { + } + } + + return result; + } + + @Transactional + @Override + public void remove( + final Groupable groupable, + final Membership membership) { + + switch (anyTypeKind) { + case USER -> { + ((User) groupable).remove((UMembership) membership); + membership.setLeftEnd(null); + groupable.getPlainAttrs(membership).forEach(groupable::remove); + userDAO.deleteMembership((UMembership) membership); + } + + case ANY_OBJECT -> { + ((AnyObject) groupable).remove((AMembership) membership); + membership.setLeftEnd(null); + groupable.getPlainAttrs(membership).forEach(groupable::remove); + anyObjectDAO.deleteMembership((AMembership) membership); + } + + default -> { + } + } + } + + @Transactional + @Override + public Relationship add( final Relatable relatable, final RelationshipType relationshipType, final AnyObject otherEnd) { + Relationship result = null; + switch (anyTypeKind) { case USER -> { URelationship urelationship = entityFactory.newEntity(URelationship.class); @@ -358,6 +427,8 @@ public void addRelationship( urelationship.setLeftEnd((User) relatable); ((User) relatable).add(urelationship); + + result = urelationship; } case GROUP -> { @@ -367,6 +438,8 @@ public void addRelationship( grelationship.setLeftEnd((Group) relatable); ((Group) relatable).add(grelationship); + + result = grelationship; } case ANY_OBJECT -> { @@ -376,23 +449,39 @@ public void addRelationship( arelationship.setLeftEnd((AnyObject) relatable); ((AnyObject) relatable).add(arelationship); + + result = arelationship; } default -> { } } + + return result; } @Transactional @Override - public void removeRelationship( + public void remove( final Relatable relatable, - final RelationshipType relationshipType, - final String otherEndKey) { + final Relationship relationship) { - relatable.getRelationship(relationshipType, otherEndKey).ifPresent(relationship -> { - relatable.getRelationships().remove(relationship); - relationship.setLeftEnd(null); - }); + relatable.getPlainAttrs(relationship).forEach(relatable::remove); + relatable.remove(relationship); + relationship.setLeftEnd(null); + + switch (anyTypeKind) { + case USER -> + userDAO.deleteRelationship((URelationship) relationship); + + case GROUP -> + groupDAO.deleteRelationship((GRelationship) relationship); + + case ANY_OBJECT -> + anyObjectDAO.deleteRelationship((ARelationship) relationship); + + default -> { + } + } } } diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/UMembershipType.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/UMembershipType.java index b3114367996..2d0a30ababc 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/UMembershipType.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/UMembershipType.java @@ -18,10 +18,13 @@ */ package org.apache.syncope.core.persistence.common.entity; +import java.util.List; +import java.util.Optional; import org.apache.syncope.core.persistence.api.ApplicationContextProvider; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public class UMembershipType implements RelationshipType { @@ -74,4 +77,19 @@ public AnyType getRightEndAnyType() { public void setRightEndAnyType(final AnyType anyType) { // cannot be changed } + + @Override + public boolean add(final RelationshipTypeExtension typeExtension) { + return false; + } + + @Override + public Optional getTypeExtension(final AnyType anyType) { + return Optional.empty(); + } + + @Override + public List getTypeExtensions() { + return List.of(); + } } diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java index 63ab8b69e64..766f6fcca84 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java @@ -29,29 +29,11 @@ import org.apache.syncope.core.persistence.api.entity.Membership; import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.PlainSchema; -import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.Relatable; +import org.apache.syncope.core.persistence.api.entity.Relationship; public class AnyValidator extends AbstractValidator { - private static boolean raiseNotAllowedViolation( - final ConstraintValidatorContext context, - final String schema, - final Group group) { - - if (group == null) { - context.buildConstraintViolationWithTemplate( - getTemplate(EntityViolationType.InvalidPlainAttr, - schema + " not allowed for this instance")). - addPropertyNode("plainAttrs").addConstraintViolation(); - } else { - context.buildConstraintViolationWithTemplate( - getTemplate(EntityViolationType.InvalidPlainAttr, - schema + " not allowed for membership of group " + group.getName())). - addPropertyNode("plainAttrs").addConstraintViolation(); - } - return false; - } - @Override public boolean isValid(final Any any, final ConstraintValidatorContext context) { context.disableDefaultConstraintViolation(); @@ -62,18 +44,44 @@ public boolean isValid(final Any any, final ConstraintValidatorContext context) for (PlainAttr attr : any.getPlainAttrs()) { String plainSchema = Optional.ofNullable(attr).map(PlainAttr::getSchema).orElse(null); - if (plainSchema != null && !allowedPlainSchemas.forSelfContains(plainSchema)) { - return raiseNotAllowedViolation(context, plainSchema, null); + if (plainSchema != null && !allowedPlainSchemas.selfContains(plainSchema)) { + context.buildConstraintViolationWithTemplate( + getTemplate(EntityViolationType.InvalidPlainAttr, + plainSchema + " not allowed for this instance")). + addPropertyNode("plainAttrs").addConstraintViolation(); + return false; + } + } + if (any instanceof Groupable groupable) { + for (Membership membership : groupable.getMemberships()) { + for (PlainAttr attr : groupable.getPlainAttrs(membership)) { + String plainSchema = Optional.ofNullable(attr).map(PlainAttr::getSchema).orElse(null); + if (plainSchema != null + && !allowedPlainSchemas.membershipsContains(membership.getRightEnd(), plainSchema)) { + + context.buildConstraintViolationWithTemplate( + getTemplate(EntityViolationType.InvalidPlainAttr, + plainSchema + " not allowed for membership of group " + + membership.getRightEnd().getName())). + addPropertyNode("plainAttrs").addConstraintViolation(); + return false; + } + } } } - if (any instanceof Groupable groupableRelatable) { - for (Membership membership : groupableRelatable.getMemberships()) { - for (PlainAttr attr : groupableRelatable.getPlainAttrs(membership)) { + if (any instanceof Relatable relatable) { + for (Relationship relationship : relatable.getRelationships()) { + for (PlainAttr attr : relatable.getPlainAttrs(relationship)) { String plainSchema = Optional.ofNullable(attr).map(PlainAttr::getSchema).orElse(null); if (plainSchema != null - && !allowedPlainSchemas.forMembershipsContains(membership.getRightEnd(), plainSchema)) { + && !allowedPlainSchemas.relationshipTypesContains(relationship.getType(), plainSchema)) { - return raiseNotAllowedViolation(context, plainSchema, membership.getRightEnd()); + context.buildConstraintViolationWithTemplate( + getTemplate(EntityViolationType.InvalidPlainAttr, + plainSchema + " not allowed for relationships of type " + + relationship.getType().getKey())). + addPropertyNode("plainAttrs").addConstraintViolation(); + return false; } } } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java index cf2dda8e13d..b95d0c32b40 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java @@ -437,6 +437,7 @@ public AnyTypeClassRepoExt anyTypeClassRepoExt( final PlainSchemaDAO plainSchemaDAO, final DerSchemaDAO derSchemaDAO, final @Lazy GroupDAO groupDAO, + final @Lazy RelationshipTypeDAO relationshipTypeDAO, final ExternalResourceDAO resourceDAO, final EntityManager entityManager) { @@ -445,6 +446,7 @@ public AnyTypeClassRepoExt anyTypeClassRepoExt( plainSchemaDAO, derSchemaDAO, groupDAO, + relationshipTypeDAO, resourceDAO, entityManager); } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java index bd2e29ff3ae..c97c3d1de4c 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java @@ -42,6 +42,8 @@ import org.apache.syncope.core.persistence.api.entity.DerSchema; import org.apache.syncope.core.persistence.api.entity.DynRealm; import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.Relationship; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.Schema; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; @@ -159,34 +161,59 @@ public AllowedSchemas findAllowedSchemas(final A any, fina typeOwnClasses.forEach(typeClass -> { if (reference.equals(PlainSchema.class)) { - result.getForSelf().addAll((Collection) typeClass.getPlainSchemas()); + result.self().addAll((Collection) typeClass.getPlainSchemas()); } else if (reference.equals(DerSchema.class)) { - result.getForSelf().addAll((Collection) typeClass.getDerSchemas()); + result.self().addAll((Collection) typeClass.getDerSchemas()); } }); - // schemas given by type extensions - Map> typeExtensionClasses = new HashMap<>(); + // schemas given by group type extensions + Map> gTypeExtensionClasses = new HashMap<>(); switch (any) { case User user -> user.getMemberships().forEach(memb -> memb.getRightEnd().getTypeExtensions(). - forEach(typeExt -> typeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses()))); + forEach(typeExt -> gTypeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses()))); case AnyObject anyObject -> anyObject.getMemberships().forEach(memb -> memb.getRightEnd().getTypeExtensions().stream(). filter(typeExt -> any.getType().equals(typeExt.getAnyType())). - forEach(typeExt -> typeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses()))); + forEach(typeExt -> gTypeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses()))); default -> { } } + gTypeExtensionClasses.entrySet().stream().peek( + entry -> result.memberships().put(entry.getKey(), new HashSet<>())). + forEach(entry -> entry.getValue().forEach(typeClass -> { + if (reference.equals(PlainSchema.class)) { + result.memberships().get(entry.getKey()). + addAll((Collection) typeClass.getPlainSchemas()); + } else if (reference.equals(DerSchema.class)) { + result.memberships().get(entry.getKey()). + addAll((Collection) typeClass.getDerSchemas()); + } + })); - typeExtensionClasses.entrySet().stream().peek( - entry -> result.getForMemberships().put(entry.getKey(), new HashSet<>())). + // schemas given by relationship type extensions + Map> rTypeExtensionClasses = new HashMap<>(); + switch (any) { + case User user -> + user.getRelationships().stream().map(Relationship::getType).distinct(). + forEach(rt -> rt.getTypeExtensions(). + forEach(typeExt -> rTypeExtensionClasses.put(rt, typeExt.getAuxClasses()))); + case AnyObject anyObject -> + anyObject.getRelationships().stream().map(Relationship::getType).distinct(). + forEach(rt -> rt.getTypeExtensions(). + forEach(typeExt -> rTypeExtensionClasses.put(rt, typeExt.getAuxClasses()))); + default -> { + } + } + rTypeExtensionClasses.entrySet().stream().peek( + entry -> result.relationshipTypes().put(entry.getKey(), new HashSet<>())). forEach(entry -> entry.getValue().forEach(typeClass -> { if (reference.equals(PlainSchema.class)) { - result.getForMemberships().get(entry.getKey()). + result.relationshipTypes().get(entry.getKey()). addAll((Collection) typeClass.getPlainSchemas()); } else if (reference.equals(DerSchema.class)) { - result.getForMemberships().get(entry.getKey()). + result.relationshipTypes().get(entry.getKey()). addAll((Collection) typeClass.getDerSchemas()); } })); @@ -211,6 +238,11 @@ public List findDynRealms(final String key) { toList(); } + @Override + public void deleteRelationship(final Relationship relationship) { + entityManager.remove(relationship); + } + protected void checkBeforeSave(final T attributable) { // check UNIQUE constraints new ArrayList<>(attributable.getPlainAttrsList()).stream(). diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExt.java index c53a9b01124..1a72f775849 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExt.java @@ -39,8 +39,6 @@ public interface AnyObjectRepoExt extends AnyRepoExt { Map countByRealm(AnyType anyType); - AMembership findMembership(String key); - void deleteMembership(AMembership membership); List findDynGroups(String key); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java index ae9ffad0eb3..aa8e04ed5c2 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java @@ -51,7 +51,6 @@ import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.api.utils.RealmUtils; import org.apache.syncope.core.persistence.common.dao.AnyFinder; -import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership; import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship; import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject; import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship; @@ -150,11 +149,6 @@ protected void securityChecks(final AnyObject anyObject) { securityChecks(authRealms, anyObject.getKey(), anyObject.getRealm().getFullPath(), findAllGroupKeys(anyObject)); } - @Override - public AMembership findMembership(final String key) { - return entityManager.find(JPAAMembership.class, key); - } - @Override public void deleteMembership(final AMembership membership) { entityManager.remove(membership); @@ -281,13 +275,13 @@ public void delete(final AnyObject anyObject) { dynRealmDAO.removeDynMemberships(anyObject.getKey()); findARelationships(anyObject).forEach(relationship -> { - relationship.getLeftEnd().getRelationships().remove(relationship); + relationship.getLeftEnd().remove(relationship); save(relationship.getLeftEnd()); entityManager.remove(relationship); }); findURelationships(anyObject).forEach(relationship -> { - relationship.getLeftEnd().getRelationships().remove(relationship); + relationship.getLeftEnd().remove(relationship); userDAO.save(relationship.getLeftEnd()); entityManager.remove(relationship); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java index 3e28c17b3d3..46f65686d19 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java @@ -24,7 +24,9 @@ import java.util.Optional; import org.apache.syncope.core.persistence.api.dao.AllowedSchemas; import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.Schema; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; public interface AnyRepoExt { @@ -40,6 +42,8 @@ public interface AnyRepoExt { Collection findAllResourceKeys(String key); + void deleteRelationship(Relationship relationship); + S save(S any); void deleteById(String key); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyTypeClassRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyTypeClassRepoExtImpl.java index 08550b2b63a..4322d89a818 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyTypeClassRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyTypeClassRepoExtImpl.java @@ -25,11 +25,13 @@ import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.DerSchema; import org.apache.syncope.core.persistence.api.entity.PlainSchema; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.jpa.entity.JPAAnyTypeClass; public class AnyTypeClassRepoExtImpl implements AnyTypeClassRepoExt { @@ -42,6 +44,8 @@ public class AnyTypeClassRepoExtImpl implements AnyTypeClassRepoExt { protected final GroupDAO groupDAO; + protected final RelationshipTypeDAO relationshipTypeDAO; + protected final ExternalResourceDAO resourceDAO; protected final EntityManager entityManager; @@ -51,6 +55,7 @@ public AnyTypeClassRepoExtImpl( final PlainSchemaDAO plainSchemaDAO, final DerSchemaDAO derSchemaDAO, final GroupDAO groupDAO, + final RelationshipTypeDAO relationshipTypeDAO, final ExternalResourceDAO resourceDAO, final EntityManager entityManager) { @@ -58,6 +63,7 @@ public AnyTypeClassRepoExtImpl( this.plainSchemaDAO = plainSchemaDAO; this.derSchemaDAO = derSchemaDAO; this.groupDAO = groupDAO; + this.relationshipTypeDAO = relationshipTypeDAO; this.resourceDAO = resourceDAO; this.entityManager = entityManager; } @@ -94,7 +100,7 @@ public void deleteById(final String key) { type.getClasses().remove(anyTypeClass); } - for (TypeExtension typeExt : groupDAO.findTypeExtensions(anyTypeClass)) { + for (GroupTypeExtension typeExt : groupDAO.findTypeExtensions(anyTypeClass)) { typeExt.getAuxClasses().remove(anyTypeClass); if (typeExt.getAuxClasses().isEmpty()) { @@ -102,6 +108,14 @@ public void deleteById(final String key) { typeExt.setGroup(null); } } + for (RelationshipTypeExtension typeExt : relationshipTypeDAO.findTypeExtensions(anyTypeClass)) { + typeExt.getAuxClasses().remove(anyTypeClass); + + if (typeExt.getAuxClasses().isEmpty()) { + typeExt.getRelationshipType().getTypeExtensions().remove(typeExt); + typeExt.setRelationshipType(null); + } + } resourceDAO.findAll().stream(). flatMap(resource -> resource.getProvisions().stream()). diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java index 30be4c24b0f..2068fea1203 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java @@ -26,7 +26,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.springframework.data.domain.Pageable; @@ -53,7 +53,7 @@ public interface GroupRepoExt extends AnyRepoExt { Group saveAndRefreshDynMemberships(Group group); - List findTypeExtensions(AnyTypeClass anyTypeClass); + List findTypeExtensions(AnyTypeClass anyTypeClass); long countADynMembers(Group group); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java index bb561b151f8..54820640cb0 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java @@ -51,7 +51,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -62,7 +62,7 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership; import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership; import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup; -import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension; +import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroupTypeExtension; import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership; import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership; import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent; @@ -338,10 +338,10 @@ public void delete(final Group group) { } @Override - public List findTypeExtensions(final AnyTypeClass anyTypeClass) { - TypedQuery query = entityManager.createQuery( - "SELECT e FROM " + JPATypeExtension.class.getSimpleName() - + " e WHERE :anyTypeClass MEMBER OF e.auxClasses", TypeExtension.class); + public List findTypeExtensions(final AnyTypeClass anyTypeClass) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM " + JPAGroupTypeExtension.class.getSimpleName() + + " e WHERE :anyTypeClass MEMBER OF e.auxClasses", GroupTypeExtension.class); query.setParameter("anyTypeClass", anyTypeClass); return query.getResultList(); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExt.java index d64357b2271..4b06ddcd2e0 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExt.java @@ -20,7 +20,9 @@ import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public interface RelationshipTypeRepoExt { @@ -28,5 +30,7 @@ public interface RelationshipTypeRepoExt { List findByLeftEndAnyType(AnyType anyType); + List findTypeExtensions(AnyTypeClass anyTypeClass); + void deleteById(String key); } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExtImpl.java index ee9da70b313..77a98dd190a 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/RelationshipTypeRepoExtImpl.java @@ -24,13 +24,17 @@ import java.util.Collection; import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; +import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.jpa.entity.JPARelationshipType; +import org.apache.syncope.core.persistence.jpa.entity.JPARelationshipTypeExtension; import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship; import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship; @@ -62,6 +66,16 @@ public List findByLeftEndAnyType(final AnyType anyTy return query.getResultList(); } + @Override + public List findTypeExtensions(final AnyTypeClass anyTypeClass) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM " + JPARelationshipTypeExtension.class.getSimpleName() + + " e WHERE :anyTypeClass MEMBER OF e.auxClasses", RelationshipTypeExtension.class); + query.setParameter("anyTypeClass", anyTypeClass); + + return query.getResultList(); + } + protected Collection> findRelationshipsByType(final RelationshipType type) { TypedQuery aquery = entityManager.createQuery( "SELECT e FROM " + JPAARelationship.class.getSimpleName() + " e WHERE e.type=:type", @@ -89,11 +103,13 @@ public void deleteById(final String key) { findRelationshipsByType(type).stream().peek(relationship -> { switch (relationship) { case URelationship uRelationship -> - uRelationship.getLeftEnd().getRelationships().remove(uRelationship); + uRelationship.getLeftEnd().remove(uRelationship); + case ARelationship aRelationship -> + aRelationship.getLeftEnd().remove(aRelationship); + case GRelationship gRelationship -> + gRelationship.getLeftEnd().remove(gRelationship); case UMembership uMembership -> uMembership.getLeftEnd().remove(uMembership); - case ARelationship aRelationship -> - aRelationship.getLeftEnd().getRelationships().remove(aRelationship); case AMembership aMembership -> aMembership.getLeftEnd().remove(aMembership); default -> { diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExt.java index 6d399334121..f91d920675f 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExt.java @@ -37,8 +37,6 @@ public interface UserRepoExt extends AnyRepoExt { Map countByStatus(); - UMembership findMembership(String key); - void deleteMembership(UMembership membership); List findDynRoles(String key); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java index 4446956a778..1a0502052ef 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java @@ -48,7 +48,6 @@ import org.apache.syncope.core.persistence.api.utils.RealmUtils; import org.apache.syncope.core.persistence.common.dao.AnyFinder; import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount; -import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership; import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser; import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.spring.security.DelegatedAdministrationException; @@ -164,11 +163,6 @@ protected void securityChecks(final User user) { } } - @Override - public UMembership findMembership(final String key) { - return entityManager.find(JPAUMembership.class, key); - } - @Override public void deleteMembership(final UMembership membership) { entityManager.remove(membership); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java index 1dfbc28225e..25f86b426ce 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java @@ -42,6 +42,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.api.entity.Remediation; import org.apache.syncope.core.persistence.api.entity.Report; import org.apache.syncope.core.persistence.api.entity.ReportExec; @@ -63,7 +64,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.keymaster.ConfParam; import org.apache.syncope.core.persistence.api.entity.keymaster.DomainEntity; import org.apache.syncope.core.persistence.api.entity.keymaster.NetworkServiceEntity; @@ -110,7 +111,7 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject; import org.apache.syncope.core.persistence.jpa.entity.group.JPAGRelationship; import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup; -import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension; +import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroupTypeExtension; import org.apache.syncope.core.persistence.jpa.entity.keymaster.JPAConfParam; import org.apache.syncope.core.persistence.jpa.entity.keymaster.JPADomain; import org.apache.syncope.core.persistence.jpa.entity.keymaster.JPANetworkService; @@ -184,10 +185,12 @@ public E newEntity(final Class reference) { result = (E) new JPAUser(); } else if (reference.equals(Group.class)) { result = (E) new JPAGroup(); - } else if (reference.equals(TypeExtension.class)) { - result = (E) new JPATypeExtension(); + } else if (reference.equals(GroupTypeExtension.class)) { + result = (E) new JPAGroupTypeExtension(); } else if (reference.equals(RelationshipType.class)) { result = (E) new JPARelationshipType(); + } else if (reference.equals(RelationshipTypeExtension.class)) { + result = (E) new JPARelationshipTypeExtension(); } else if (reference.equals(ARelationship.class)) { result = (E) new JPAARelationship(); } else if (reference.equals(URelationship.class)) { diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java index c6e3417a441..fb642236d43 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.core.persistence.jpa.entity; -import java.util.Collection; import java.util.List; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.Any; @@ -31,22 +30,22 @@ public abstract class AbstractGroupableRelatable< L extends Any, M extends Membership, - REL extends Relationship> - extends AbstractRelatable implements Groupable { + R extends Relationship> + extends AbstractRelatable implements Groupable { private static final long serialVersionUID = -2269285197388729673L; @Override public List getPlainAttrs() { return getPlainAttrsList().stream(). - filter(attr -> attr.getMembership() == null). + filter(attr -> attr.getMembership() == null && attr.getRelationship() == null). toList(); } @Override public Optional getPlainAttr(final String plainSchema) { return getPlainAttrsList().stream(). - filter(attr -> attr.getMembership() == null + filter(attr -> attr.getMembership() == null && attr.getRelationship() == null && plainSchema.equals(attr.getSchema())). findFirst(); } @@ -60,14 +59,7 @@ public Optional getPlainAttr(final String plainSchema, final Membersh } @Override - public Collection getPlainAttrs(final String plainSchema) { - return getPlainAttrsList().stream(). - filter(attr -> plainSchema.equals(attr.getSchema())). - toList(); - } - - @Override - public Collection getPlainAttrs(final Membership membership) { + public List getPlainAttrs(final Membership membership) { return getPlainAttrsList().stream(). filter(attr -> membership.getKey().equals(attr.getMembership())). toList(); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java index ea66591fa77..06ceb2973f2 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java @@ -18,9 +18,10 @@ */ package org.apache.syncope.core.persistence.jpa.entity; -import java.util.Collection; +import java.util.List; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.Relatable; import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; @@ -28,13 +29,28 @@ public abstract class AbstractRelatable< L extends Any, - REL extends Relationship> - extends AbstractAny implements Relatable { + R extends Relationship> + extends AbstractAny implements Relatable { private static final long serialVersionUID = -2269285197388729673L; @Override - public Optional getRelationship( + public Optional getPlainAttr(final String plainSchema, final Relationship relationship) { + return getPlainAttrsList().stream(). + filter(attr -> plainSchema.equals(attr.getSchema()) + && relationship.getKey().equals(attr.getRelationship())). + findFirst(); + } + + @Override + public List getPlainAttrs(final Relationship relationship) { + return getPlainAttrsList().stream(). + filter(attr -> relationship.getKey().equals(attr.getRelationship())). + toList(); + } + + @Override + public Optional getRelationship( final RelationshipType relationshipType, final String otherEndKey) { return getRelationships().stream().filter(relationship -> relationshipType.equals(relationship.getType()) @@ -44,14 +60,14 @@ public Optional getRelationship( } @Override - public Collection getRelationships(final RelationshipType relationshipType) { + public List getRelationships(final RelationshipType relationshipType) { return getRelationships().stream(). filter(relationship -> relationshipType.equals(relationship.getType())). toList(); } @Override - public Collection getRelationships(final String otherEndKey) { + public List getRelationships(final String otherEndKey) { return getRelationships().stream(). filter(relationship -> otherEndKey.equals(relationship.getRightEnd().getKey())). toList(); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPATypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java similarity index 66% rename from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPATypeExtension.java rename to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java index fbdc2da7d2b..107f1693cf7 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPATypeExtension.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java @@ -16,37 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.core.persistence.jpa.entity.group; +package org.apache.syncope.core.persistence.jpa.entity; -import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; +import jakarta.persistence.MappedSuperclass; import jakarta.persistence.UniqueConstraint; import java.util.ArrayList; import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; -import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; -import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity; -import org.apache.syncope.core.persistence.jpa.entity.JPAAnyType; -import org.apache.syncope.core.persistence.jpa.entity.JPAAnyTypeClass; +import org.apache.syncope.core.persistence.api.entity.TypeExtension; -@Entity -@Table(name = JPATypeExtension.TABLE, uniqueConstraints = - @UniqueConstraint(columnNames = { "group_id", "anyType_id" })) -public class JPATypeExtension extends AbstractGeneratedKeyEntity implements TypeExtension { +@MappedSuperclass +public abstract class AbstractTypeExtension extends AbstractGeneratedKeyEntity implements TypeExtension { - private static final long serialVersionUID = -8367626793791263551L; - - public static final String TABLE = "TypeExtension"; - - @ManyToOne - private JPAGroup group; + private static final long serialVersionUID = -840023594563575766L; @ManyToOne private JPAAnyType anyType; @@ -60,17 +48,6 @@ public class JPATypeExtension extends AbstractGeneratedKeyEntity implements Type @UniqueConstraint(columnNames = { "typeExtension_id", "anyTypeClass_id" })) private List auxClasses = new ArrayList<>(); - @Override - public Group getGroup() { - return group; - } - - @Override - public void setGroup(final Group group) { - checkType(group, JPAGroup.class); - this.group = (JPAGroup) group; - } - @Override public AnyType getAnyType() { return anyType; diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipType.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipType.java index f6c62952d05..2043296542e 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipType.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipType.java @@ -19,12 +19,19 @@ package org.apache.syncope.core.persistence.jpa.entity; import jakarta.persistence.Cacheable; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.common.validation.RelationshipTypeCheck; @Entity @@ -47,6 +54,9 @@ public class JPARelationshipType extends AbstractProvidedKeyEntity implements Re @ManyToOne private JPAAnyType rightEndAnyType; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "relationshipType") + private List typeExtensions = new ArrayList<>(); + @Override public String getDescription() { return description; @@ -78,4 +88,22 @@ public void setRightEndAnyType(final AnyType anyType) { checkType(anyType, JPAAnyType.class); this.rightEndAnyType = (JPAAnyType) anyType; } + + @Override + public boolean add(final RelationshipTypeExtension typeExtension) { + checkType(typeExtension, JPARelationshipTypeExtension.class); + return typeExtensions.add((JPARelationshipTypeExtension) typeExtension); + } + + @Override + public Optional getTypeExtension(final AnyType anyType) { + return typeExtensions.stream(). + filter(typeExtension -> typeExtension.getAnyType().equals(anyType)). + findFirst(); + } + + @Override + public List getTypeExtensions() { + return typeExtensions; + } } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java new file mode 100644 index 00000000000..ad9013edb1c --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java @@ -0,0 +1,50 @@ +/* + * 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.core.persistence.jpa.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; + +@Entity +@Table(name = JPARelationshipTypeExtension.TABLE, uniqueConstraints = + @UniqueConstraint(columnNames = { "relationshipType_id", "anyType_id" })) +public class JPARelationshipTypeExtension extends AbstractTypeExtension implements RelationshipTypeExtension { + + private static final long serialVersionUID = -8367626793791263551L; + + public static final String TABLE = "RelationshipTypeExtension"; + + @ManyToOne + private JPARelationshipType relationshipType; + + @Override + public RelationshipType getRelationshipType() { + return relationshipType; + } + + @Override + public void setRelationshipType(final RelationshipType relationshipType) { + checkType(relationshipType, JPARelationshipType.class); + this.relationshipType = (JPARelationshipType) relationshipType; + } +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAMembership.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAMembership.java index f0199a1f81c..ecb0a85344c 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAMembership.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAMembership.java @@ -24,6 +24,7 @@ import jakarta.persistence.PostLoad; import jakarta.persistence.Table; import jakarta.persistence.Transient; +import jakarta.persistence.UniqueConstraint; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; @@ -34,7 +35,8 @@ import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup; @Entity -@Table(name = JPAAMembership.TABLE) +@Table(name = JPAAMembership.TABLE, uniqueConstraints = + @UniqueConstraint(columnNames = { "anyObject_id", "group_id" })) public class JPAAMembership extends AbstractGeneratedKeyEntity implements AMembership { private static final long serialVersionUID = 1503557547394601405L; diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java index 623996dbd9f..6671b3443ce 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java @@ -40,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; @@ -161,7 +162,7 @@ public boolean remove(final PlainAttr attr) { @Override public boolean add(final AnyTypeClass auxClass) { checkType(auxClass, JPAAnyTypeClass.class); - return auxClasses.contains((JPAAnyTypeClass) auxClass) || this.auxClasses.add((JPAAnyTypeClass) auxClass); + return auxClasses.contains((JPAAnyTypeClass) auxClass) || auxClasses.add((JPAAnyTypeClass) auxClass); } @Override @@ -172,7 +173,14 @@ public List getAuxClasses() { @Override public boolean add(final ARelationship relationship) { checkType(relationship, JPAARelationship.class); - return this.relationships.add((JPAARelationship) relationship); + return relationships.add((JPAARelationship) relationship); + } + + @Override + public boolean remove(final Relationship relationship) { + checkType(relationship, JPAARelationship.class); + plainAttrsList.removeIf(attr -> Objects.equals(attr.getRelationship(), relationship.getKey())); + return relationships.remove((JPAARelationship) relationship); } @Override @@ -183,14 +191,14 @@ public List getRelationships() { @Override public boolean add(final AMembership membership) { checkType(membership, JPAAMembership.class); - return this.memberships.add((JPAAMembership) membership); + return memberships.add((JPAAMembership) membership); } @Override public boolean remove(final AMembership membership) { checkType(membership, JPAAMembership.class); plainAttrsList.removeIf(attr -> Objects.equals(attr.getMembership(), membership.getKey())); - return this.memberships.remove((JPAAMembership) membership); + return memberships.remove((JPAAMembership) membership); } @Override diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java index da5cee7af7f..a99a87b62ae 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java @@ -37,6 +37,7 @@ import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.apache.syncope.core.persistence.api.ApplicationContextProvider; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; @@ -44,10 +45,11 @@ import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.common.validation.GroupCheck; @@ -112,7 +114,7 @@ public class JPAGroup private List aDynMemberships = new ArrayList<>(); @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "group") - private List typeExtensions = new ArrayList<>(); + private List typeExtensions = new ArrayList<>(); @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "leftEnd") @Valid @@ -222,7 +224,7 @@ public void setUDynMembership(final UDynGroupMembership uDynMembership) { @Override public boolean add(final AnyTypeClass auxClass) { checkType(auxClass, JPAAnyTypeClass.class); - return auxClasses.contains((JPAAnyTypeClass) auxClass) || this.auxClasses.add((JPAAnyTypeClass) auxClass); + return auxClasses.contains((JPAAnyTypeClass) auxClass) || auxClasses.add((JPAAnyTypeClass) auxClass); } @Override @@ -233,7 +235,7 @@ public List getAuxClasses() { @Override public boolean add(final ADynGroupMembership dynGroupMembership) { checkType(dynGroupMembership, JPAADynGroupMembership.class); - return this.aDynMemberships.add((JPAADynGroupMembership) dynGroupMembership); + return aDynMemberships.add((JPAADynGroupMembership) dynGroupMembership); } @Override @@ -249,27 +251,34 @@ public List getADynMemberships() { } @Override - public boolean add(final TypeExtension typeExtension) { - checkType(typeExtension, JPATypeExtension.class); - return this.typeExtensions.add((JPATypeExtension) typeExtension); + public boolean add(final GroupTypeExtension typeExtension) { + checkType(typeExtension, JPAGroupTypeExtension.class); + return typeExtensions.add((JPAGroupTypeExtension) typeExtension); } @Override - public Optional getTypeExtension(final AnyType anyType) { + public Optional getTypeExtension(final AnyType anyType) { return typeExtensions.stream(). filter(typeExtension -> typeExtension.getAnyType().equals(anyType)). findFirst(); } @Override - public List getTypeExtensions() { + public List getTypeExtensions() { return typeExtensions; } @Override public boolean add(final GRelationship relationship) { checkType(relationship, JPAGRelationship.class); - return this.relationships.add((JPAGRelationship) relationship); + return relationships.add((JPAGRelationship) relationship); + } + + @Override + public boolean remove(final Relationship relationship) { + checkType(relationship, JPAGRelationship.class); + plainAttrsList.removeIf(attr -> Objects.equals(attr.getRelationship(), relationship.getKey())); + return relationships.remove((JPAGRelationship) relationship); } @Override diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java new file mode 100644 index 00000000000..bfcf9173827 --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java @@ -0,0 +1,51 @@ +/* + * 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.core.persistence.jpa.entity.group; + +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; +import org.apache.syncope.core.persistence.jpa.entity.AbstractTypeExtension; + +@Entity +@Table(name = JPAGroupTypeExtension.TABLE, uniqueConstraints = + @UniqueConstraint(columnNames = { "group_id", "anyType_id" })) +public class JPAGroupTypeExtension extends AbstractTypeExtension implements GroupTypeExtension { + + private static final long serialVersionUID = -8367626793791263551L; + + public static final String TABLE = "GroupTypeExtension"; + + @ManyToOne + private JPAGroup group; + + @Override + public Group getGroup() { + return group; + } + + @Override + public void setGroup(final Group group) { + checkType(group, JPAGroup.class); + this.group = (JPAGroup) group; + } +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUMembership.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUMembership.java index 3c0e2fdc33c..d26dbd9d2a8 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUMembership.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUMembership.java @@ -22,6 +22,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.user.UMembership; @@ -31,7 +32,8 @@ import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup; @Entity -@Table(name = JPAUMembership.TABLE) +@Table(name = JPAUMembership.TABLE, uniqueConstraints = + @UniqueConstraint(columnNames = { "user_id", "group_id" })) public class JPAUMembership extends AbstractGeneratedKeyEntity implements UMembership { private static final long serialVersionUID = -14584450896965100L; diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java index d6cd8419b4c..196c7a91978 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java @@ -52,6 +52,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.Role; import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion; @@ -452,6 +453,13 @@ public boolean add(final URelationship relationship) { return this.relationships.add((JPAURelationship) relationship); } + @Override + public boolean remove(final Relationship relationship) { + checkType(relationship, JPAURelationship.class); + plainAttrsList.removeIf(attr -> Objects.equals(attr.getRelationship(), relationship.getKey())); + return relationships.remove((JPAURelationship) relationship); + } + @Override public List getRelationships() { return relationships; @@ -460,14 +468,14 @@ public List getRelationships() { @Override public boolean add(final UMembership membership) { checkType(membership, JPAUMembership.class); - return this.memberships.add((JPAUMembership) membership); + return memberships.add((JPAUMembership) membership); } @Override public boolean remove(final UMembership membership) { checkType(membership, JPAUMembership.class); plainAttrsList.removeIf(attr -> Objects.equals(attr.getMembership(), membership.getKey())); - return this.memberships.remove((JPAUMembership) membership); + return memberships.remove((JPAUMembership) membership); } @Override diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java index ad8a4ea9777..37477b21001 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java @@ -36,7 +36,6 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO; import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; -import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.jpa.AbstractTest; import org.apache.syncope.core.spring.security.PasswordGenerator; @@ -149,13 +148,6 @@ public void findByUsername() { assertTrue(userDAO.findByUsername("user6").isEmpty()); } - @Test - public void findMembership() { - UMembership memb = userDAO.findMembership("3d5e91f6-305e-45f9-ad30-4897d3d43bd9"); - assertNotNull(memb); - assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", memb.getLeftEnd().getKey()); - } - @Test public void save() { User user = entityFactory.newEntity(User.class); diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java index f41d052cbdf..dea60cddf42 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java @@ -51,7 +51,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -188,7 +188,7 @@ public void create() { group.setRealm(realmDAO.getRoot()); group.setName("new"); - TypeExtension typeExt = entityFactory.newEntity(TypeExtension.class); + GroupTypeExtension typeExt = entityFactory.newEntity(GroupTypeExtension.class); typeExt.setAnyType(anyTypeDAO.getUser()); typeExt.add(anyTypeClassDAO.findById("csv").orElseThrow()); typeExt.add(anyTypeClassDAO.findById("other").orElseThrow()); diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java index b4bb2186df4..52a7fecf623 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java @@ -134,7 +134,7 @@ public void ships() { "fc6dbc3a-6c07-4965-8781-921e7401a4a5", user.getRelationships().getFirst().getRightEnd().getKey()); - user.getRelationships().removeFirst(); + user.remove(user.getRelationships().getFirst()); URelationship newR = entityFactory.newEntity(URelationship.class); newR.setType(relationshipTypeDAO.findById("neighborhood").orElseThrow()); diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml index 247abcb59f3..92e4a5784f6 100644 --- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml @@ -393,9 +393,9 @@ under the License. realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" creator="admin" lastModifier="admin" creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/> - - + + - - - + + + AllowedSchemas findAllowedSchemas(final A any, fina atc.getPlainSchemas().stream(). map(schema -> plainSchemaDAO.findById(schema.getKey())). flatMap(Optional::stream). - forEach(schema -> result.getForSelf().add((S) schema)); + forEach(schema -> result.self().add((S) schema)); } else if (reference.equals(DerSchema.class)) { atc.getDerSchemas().stream(). map(schema -> derSchemaDAO.findById(schema.getKey())). flatMap(Optional::stream). - forEach(schema -> result.getForSelf().add((S) schema)); + forEach(schema -> result.self().add((S) schema)); } }); // schemas given by type extensions - Map> typeExtensionClasses = new HashMap<>(); + Map> gTypeExtensionClasses = new HashMap<>(); switch (any) { case User user -> user.getMemberships().forEach(memb -> memb.getRightEnd().getTypeExtensions().forEach(typeExt -> { @@ -213,7 +220,7 @@ public AllowedSchemas findAllowedSchemas(final A any, fina typeExtClasses.add(anyTypeClass); }); - typeExtensionClasses.put(memb.getRightEnd(), typeExtClasses); + gTypeExtensionClasses.put(memb.getRightEnd(), typeExtClasses); })); case AnyObject anyObject -> @@ -227,26 +234,73 @@ public AllowedSchemas findAllowedSchemas(final A any, fina typeExtClasses.add(anyTypeClass); }); - typeExtensionClasses.put(memb.getRightEnd(), typeExtClasses); + gTypeExtensionClasses.put(memb.getRightEnd(), typeExtClasses); })); default -> { } } + gTypeExtensionClasses.entrySet().stream().peek( + entry -> result.memberships().put(entry.getKey(), new HashSet<>())) + .forEach(entry -> entry.getValue().forEach(atc -> { + if (reference.equals(PlainSchema.class)) { + atc.getPlainSchemas().stream(). + map(schema -> plainSchemaDAO.findById(schema.getKey())). + flatMap(Optional::stream). + forEach(schema -> result.memberships().get(entry.getKey()).add((S) schema)); + } else if (reference.equals(DerSchema.class)) { + atc.getDerSchemas().stream(). + map(schema -> derSchemaDAO.findById(schema.getKey())). + flatMap(Optional::stream). + forEach(schema -> result.memberships().get(entry.getKey()).add((S) schema)); + } + })); + + // schemas given by relationship type extensions + Map> rTypeExtensionClasses = new HashMap<>(); + switch (any) { + case User user -> + user.getRelationships().forEach(rel -> rel.getType().getTypeExtensions().forEach(typeExt -> { + Set typeExtClasses = new HashSet<>(); + typeExt.getAuxClasses().forEach(atc -> { + AnyTypeClass anyTypeClass = anyTypeClassDAO.findById(atc.getKey()). + orElseThrow(() -> new NotFoundException("AnyTypeClass " + atc.getKey())); + typeExtClasses.add(anyTypeClass); + }); - typeExtensionClasses.entrySet().stream().peek( - entry -> result.getForMemberships().put(entry.getKey(), new HashSet<>())) + rTypeExtensionClasses.put(rel.getType(), typeExtClasses); + })); + + case AnyObject anyObject -> + anyObject.getRelationships().forEach(rel -> rel.getType().getTypeExtensions().stream(). + filter(typeExt -> any.getType().equals(typeExt.getAnyType())).forEach(typeExt -> { + + Set typeExtClasses = new HashSet<>(); + typeExt.getAuxClasses().forEach(atc -> { + AnyTypeClass anyTypeClass = anyTypeClassDAO.findById(atc.getKey()). + orElseThrow(() -> new NotFoundException("AnyTypeClass " + atc.getKey())); + typeExtClasses.add(anyTypeClass); + }); + + rTypeExtensionClasses.put(rel.getType(), typeExtClasses); + })); + + default -> { + } + } + rTypeExtensionClasses.entrySet().stream().peek( + entry -> result.relationshipTypes().put(entry.getKey(), new HashSet<>())) .forEach(entry -> entry.getValue().forEach(atc -> { if (reference.equals(PlainSchema.class)) { atc.getPlainSchemas().stream(). map(schema -> plainSchemaDAO.findById(schema.getKey())). flatMap(Optional::stream). - forEach(schema -> result.getForMemberships().get(entry.getKey()).add((S) schema)); + forEach(schema -> result.relationshipTypes().get(entry.getKey()).add((S) schema)); } else if (reference.equals(DerSchema.class)) { atc.getDerSchemas().stream(). map(schema -> derSchemaDAO.findById(schema.getKey())). flatMap(Optional::stream). - forEach(schema -> result.getForMemberships().get(entry.getKey()).add((S) schema)); + forEach(schema -> result.relationshipTypes().get(entry.getKey()).add((S) schema)); } })); @@ -275,6 +329,16 @@ public List findByResourcesContaining(final ExternalResource resource) { cache()); } + @Override + public void deleteRelationship(final Relationship relationship) { + var domainType = relationship instanceof URelationship + ? Neo4jURelationship.class + : relationship instanceof GRelationship + ? Neo4jGRelationship.class + : Neo4jARelationship.class; + neo4jTemplate.deleteById(relationship.getKey(), domainType); + } + protected void checkBeforeSave(final T attributable) { // check UNIQUE constraints attributable.getPlainAttrs().stream().filter(attr -> attr.getUniqueValue() != null).forEach(attr -> { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExt.java index 1a63546dbb6..565cd54ca84 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExt.java @@ -46,8 +46,6 @@ public interface AnyObjectRepoExt extends AnyRepoExt { Map countByRealm(AnyType anyType); - AMembership findMembership(String key); - void deleteMembership(AMembership membership); List findDynGroups(String key); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java index 158934b86bb..d1c5edf76f5 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java @@ -202,11 +202,6 @@ public Map countByRealm(final AnyType anyType) { return result.stream().collect(Collectors.toMap(r -> r.get("realm").toString(), r -> (Long) r.get("counted"))); } - @Override - public AMembership findMembership(final String key) { - return neo4jTemplate.findById(key, Neo4jAMembership.class).orElse(null); - } - @Override public void deleteMembership(final AMembership membership) { neo4jTemplate.deleteById(membership.getKey(), Neo4jAMembership.class); @@ -358,7 +353,7 @@ public void delete(final AnyObject anyObject) { findARelationships(anyObject).forEach(relationship -> { findById(relationship.getLeftEnd().getKey()).ifPresent(le -> { - le.getRelationships().remove(relationship); + le.remove(relationship); save(le); }); @@ -366,7 +361,7 @@ public void delete(final AnyObject anyObject) { }); findURelationships(anyObject).forEach(relationship -> { userDAO.findById(relationship.getLeftEnd().getKey()).ifPresent(le -> { - le.getRelationships().remove(relationship); + le.remove(relationship); userDAO.save(le); }); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java index a2306a8ce64..1131579be34 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java @@ -26,7 +26,9 @@ import org.apache.syncope.core.persistence.api.dao.AllowedSchemas; import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.Schema; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAMembership; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject; import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup; @@ -85,6 +87,8 @@ static String membNode(final AnyTypeKind anyTypeKind) { List findByResourcesContaining(ExternalResource resource); + void deleteRelationship(Relationship relationship); + S save(S any); void deleteById(String key); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java index 7fc5f1cc581..975f70ee51d 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java @@ -26,11 +26,13 @@ import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.DerSchema; import org.apache.syncope.core.persistence.api.entity.PlainSchema; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO; import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyType; @@ -54,6 +56,8 @@ public class AnyTypeClassRepoExtImpl extends AbstractDAO implements AnyTypeClass protected final GroupDAO groupDAO; + protected final RelationshipTypeDAO relationshipTypeDAO; + protected final ExternalResourceDAO resourceDAO; protected final NodeValidator nodeValidator; @@ -69,6 +73,7 @@ public AnyTypeClassRepoExtImpl( final PlainSchemaDAO plainSchemaDAO, final DerSchemaDAO derSchemaDAO, final GroupDAO groupDAO, + final RelationshipTypeDAO relationshipTypeDAO, final ExternalResourceDAO resourceDAO, final Neo4jTemplate neo4jTemplate, final Neo4jClient neo4jClient, @@ -83,6 +88,7 @@ public AnyTypeClassRepoExtImpl( this.plainSchemaDAO = plainSchemaDAO; this.derSchemaDAO = derSchemaDAO; this.groupDAO = groupDAO; + this.relationshipTypeDAO = relationshipTypeDAO; this.resourceDAO = resourceDAO; this.nodeValidator = nodeValidator; this.anyTypeCache = anyTypeCache; @@ -160,7 +166,7 @@ public void deleteById(final String key) { ifPresent(cached -> cached.getClasses().remove(anyTypeClass)); } - for (TypeExtension typeExt : groupDAO.findTypeExtensions(anyTypeClass)) { + for (GroupTypeExtension typeExt : groupDAO.findTypeExtensions(anyTypeClass)) { typeExt.getAuxClasses().remove(anyTypeClass); if (typeExt.getAuxClasses().isEmpty()) { @@ -170,6 +176,14 @@ public void deleteById(final String key) { typeExt.setGroup(null); } } + for (RelationshipTypeExtension typeExt : relationshipTypeDAO.findTypeExtensions(anyTypeClass)) { + typeExt.getAuxClasses().remove(anyTypeClass); + + if (typeExt.getAuxClasses().isEmpty()) { + typeExt.getRelationshipType().getTypeExtensions().remove(typeExt); + typeExt.setRelationshipType(null); + } + } resourceDAO.findAll().stream().filter(resource -> resource.getProvisions().stream(). anyMatch(provision -> provision.getAuxClasses().contains(anyTypeClass.getKey()))). diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java index bb94472cc97..059cfb25fa4 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java @@ -26,7 +26,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.springframework.data.domain.Pageable; @@ -53,7 +53,7 @@ public interface GroupRepoExt extends AnyRepoExt { Group saveAndRefreshDynMemberships(Group group); - List findTypeExtensions(AnyTypeClass anyTypeClass); + List findTypeExtensions(AnyTypeClass anyTypeClass); long countADynMembers(Group group); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java index 52e4cc28508..036a538ac46 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java @@ -54,7 +54,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -71,7 +71,7 @@ import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAMembership; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject; import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup; -import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jTypeExtension; +import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroupTypeExtension; import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUDynGroupMembership; import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUMembership; import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUser; @@ -329,10 +329,10 @@ public S save(final S group) { beforeADynMembs.removeAll(group.getADynMemberships().stream().map(ADynGroupMembership::getKey).toList()); beforeADynMembs.forEach(m -> neo4jTemplate.deleteById(m, Neo4jADynGroupMembership.class)); - Set beforeTypeExts = before.getTypeExtensions().stream().map(TypeExtension::getKey). + Set beforeTypeExts = before.getTypeExtensions().stream().map(GroupTypeExtension::getKey). collect(Collectors.toSet()); - beforeTypeExts.removeAll(group.getTypeExtensions().stream().map(TypeExtension::getKey).toList()); - beforeTypeExts.forEach(r -> neo4jTemplate.deleteById(r, Neo4jTypeExtension.class)); + beforeTypeExts.removeAll(group.getTypeExtensions().stream().map(GroupTypeExtension::getKey).toList()); + beforeTypeExts.forEach(r -> neo4jTemplate.deleteById(r, Neo4jGroupTypeExtension.class)); }); S merged = neo4jTemplate.save(nodeValidator.validate(group)); @@ -444,8 +444,7 @@ public void delete(final Group group) { Neo4jGroup.NODE, group.getKey()); - cascadeDelete( - Neo4jTypeExtension.NODE, + cascadeDelete(Neo4jGroupTypeExtension.NODE, Neo4jGroup.NODE, group.getKey()); @@ -453,12 +452,12 @@ public void delete(final Group group) { } @Override - public List findTypeExtensions(final AnyTypeClass anyTypeClass) { + public List findTypeExtensions(final AnyTypeClass anyTypeClass) { return findByRelationship( - Neo4jTypeExtension.NODE, + Neo4jGroupTypeExtension.NODE, Neo4jAnyTypeClass.NODE, anyTypeClass.getKey(), - Neo4jTypeExtension.class, + Neo4jGroupTypeExtension.class, null); } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExt.java index b839b09204b..8617b5683f0 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExt.java @@ -20,7 +20,9 @@ import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public interface RelationshipTypeRepoExt { @@ -28,5 +30,7 @@ public interface RelationshipTypeRepoExt { List findByLeftEndAnyType(AnyType anyType); + List findTypeExtensions(AnyTypeClass anyTypeClass); + void deleteById(String key); } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExtImpl.java index 6ff932906d7..246439c8dff 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RelationshipTypeRepoExtImpl.java @@ -22,15 +22,20 @@ import java.util.Collection; import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; +import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyType; +import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRelationshipType; +import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRelationshipTypeExtension; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jARelationship; import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jURelationship; import org.springframework.data.neo4j.core.Neo4jClient; @@ -60,6 +65,16 @@ public List findByLeftEndAnyType(final AnyType anyTy null); } + @Override + public List findTypeExtensions(final AnyTypeClass anyTypeClass) { + return findByRelationship( + Neo4jRelationshipTypeExtension.NODE, + Neo4jAnyTypeClass.NODE, + anyTypeClass.getKey(), + Neo4jRelationshipTypeExtension.class, + null); + } + protected Collection> findRelationshipsByType(final RelationshipType type) { List> result = new ArrayList<>(); @@ -77,11 +92,13 @@ public void deleteById(final String key) { findRelationshipsByType(type).stream().peek(relationship -> { switch (relationship) { case URelationship uRelationship -> - uRelationship.getLeftEnd().getRelationships().remove(uRelationship); + uRelationship.getLeftEnd().remove(uRelationship); + case ARelationship aRelationship -> + aRelationship.getLeftEnd().remove(aRelationship); + case GRelationship gRelationship -> + gRelationship.getLeftEnd().remove(gRelationship); case UMembership uMembership -> uMembership.getLeftEnd().remove(uMembership); - case ARelationship aRelationship -> - aRelationship.getLeftEnd().getRelationships().remove(aRelationship); case AMembership aMembership -> aMembership.getLeftEnd().remove(aMembership); default -> { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExt.java index 5afa35bea83..44bfa78da6f 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExt.java @@ -46,8 +46,6 @@ public interface UserRepoExt extends AnyRepoExt { Map countByStatus(); - UMembership findMembership(String key); - void deleteMembership(UMembership membership); List findDynRoles(String key); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java index fd5944b0033..c0ed3073420 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java @@ -221,11 +221,6 @@ public Map countByStatus() { return result.stream().collect(Collectors.toMap(r -> r.get("status").toString(), r -> (Long) r.get("counted"))); } - @Override - public UMembership findMembership(final String key) { - return neo4jTemplate.findById(key, Neo4jUMembership.class).orElse(null); - } - @Override public void deleteMembership(final UMembership membership) { neo4jTemplate.deleteById(membership.getKey(), Neo4jUMembership.class); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java index 7a1f7723c47..1cefdf824a2 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java @@ -18,10 +18,8 @@ */ package org.apache.syncope.core.persistence.neo4j.entity; -import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.Membership; @@ -33,8 +31,8 @@ public abstract class AbstractGroupableRelatable< L extends Any, M extends Membership, - REL extends Relationship> - extends AbstractRelatable implements Groupable { + R extends Relationship> + extends AbstractRelatable implements Groupable { private static final long serialVersionUID = -2269285197388729673L; @@ -42,13 +40,16 @@ public abstract class AbstractGroupableRelatable< @Override public boolean remove(final PlainAttr attr) { - if (attr.getMembership() == null) { + if (attr.getMembership() == null && attr.getRelationship() == null) { return plainAttrs().put(attr.getSchema(), null) != null; } return memberships().stream(). filter(m -> m.getKey().equals(attr.getMembership())).findFirst(). map(membership -> membership.plainAttrs().put(attr.getSchema(), null) != null). + or(() -> relationships().stream(). + filter(r -> r.getKey().equals(attr.getRelationship())).findFirst(). + map(relationship -> relationship.plainAttrs().put(attr.getSchema(), null) != null)). orElse(false); } @@ -60,14 +61,7 @@ public Optional getPlainAttr(final String plainSchema, final Membersh } @Override - public Collection getPlainAttrs(final String plainSchema) { - return Stream.concat(getPlainAttr(plainSchema).map(Stream::of).orElseGet(Stream::empty), - memberships().stream().map(m -> m.getPlainAttr(plainSchema)).flatMap(Optional::stream)). - toList(); - } - - @Override - public Collection getPlainAttrs(final Membership membership) { + public List getPlainAttrs(final Membership membership) { return memberships().stream(). filter(m -> m.getKey().equals(membership.getKey())). flatMap(m -> m.getPlainAttrs().stream()).toList(); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractMembership.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractMembership.java index bcd28798983..0301bb00a2d 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractMembership.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractMembership.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.core.persistence.neo4j.entity; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,11 +34,23 @@ public abstract class AbstractMembership protected abstract Map plainAttrs(); - public abstract List getPlainAttrs(); - - public abstract Optional getPlainAttr(String plainSchema); - - public abstract boolean add(PlainAttr attr); - - public abstract boolean remove(String plainSchema); + public List getPlainAttrs() { + return plainAttrs().entrySet().stream(). + filter(e -> e.getValue() != null). + sorted(Comparator.comparing(Map.Entry::getKey)). + map(Map.Entry::getValue).toList(); + } + + public Optional getPlainAttr(final String plainSchema) { + return Optional.ofNullable(plainAttrs().get(plainSchema)); + } + + public boolean add(final PlainAttr attr) { + return getKey().equals(attr.getMembership()) + && plainAttrs().put(attr.getSchema(), attr) != null; + } + + public boolean remove(final String plainSchema) { + return plainAttrs().put(plainSchema, null) != null; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java index 59d48a7d4c5..9d90aec9546 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java @@ -18,23 +18,41 @@ */ package org.apache.syncope.core.persistence.neo4j.entity; -import java.util.Collection; +import java.util.List; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.Relatable; import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.springframework.data.neo4j.core.schema.PostLoad; public abstract class AbstractRelatable< L extends Any, - REL extends Relationship> - extends AbstractAny implements Relatable { + R extends Relationship> + extends AbstractAny implements Relatable { private static final long serialVersionUID = -2269285197388729673L; + protected abstract List> relationships(); + + @Override + public Optional getPlainAttr(final String plainSchema, final Relationship relationship) { + return relationships().stream(). + filter(r -> r.getKey().equals(relationship.getKey())).findFirst(). + flatMap(m -> m.getPlainAttr(plainSchema)); + } + @Override - public Optional getRelationship( + public List getPlainAttrs(final Relationship relationship) { + return relationships().stream(). + filter(r -> r.getKey().equals(relationship.getKey())). + flatMap(m -> m.getPlainAttrs().stream()).toList(); + } + + @Override + public Optional getRelationship( final RelationshipType relationshipType, final String otherEndKey) { return getRelationships().stream().filter(relationship -> relationshipType.equals(relationship.getType()) @@ -42,18 +60,29 @@ public Optional getRelationship( && (otherEndKey.equals(relationship.getLeftEnd().getKey()) || otherEndKey.equals(relationship.getRightEnd().getKey()))).findFirst(); } + + @SuppressWarnings("unchecked") + @Override + public List getRelationships() { + return relationships().stream().map(r -> (R) r).toList(); + } @Override - public Collection getRelationships(final RelationshipType relationshipType) { + public List getRelationships(final RelationshipType relationshipType) { return getRelationships().stream(). filter(relationship -> relationshipType.equals(relationship.getType())). toList(); } @Override - public Collection getRelationships(final String otherEndKey) { + public List getRelationships(final String otherEndKey) { return getRelationships().stream(). filter(relationship -> otherEndKey.equals(relationship.getRightEnd().getKey())). toList(); } + + @PostLoad + public void completeRelationshipPlainAttrs() { + relationships().forEach(m -> doComplete(m.plainAttrs())); + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java new file mode 100644 index 00000000000..150ccd2c231 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java @@ -0,0 +1,56 @@ +/* + * 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.core.persistence.neo4j.entity; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Relationship; + +public abstract class AbstractRelationship + extends AbstractGeneratedKeyNode + implements Relationship { + + private static final long serialVersionUID = 6593799565567684878L; + + protected abstract Map plainAttrs(); + + public List getPlainAttrs() { + return plainAttrs().entrySet().stream(). + filter(e -> e.getValue() != null). + sorted(Comparator.comparing(Map.Entry::getKey)). + map(Map.Entry::getValue).toList(); + } + + public Optional getPlainAttr(final String plainSchema) { + return Optional.ofNullable(plainAttrs().get(plainSchema)); + } + + public boolean add(final PlainAttr attr) { + return getKey().equals(attr.getMembership()) + && plainAttrs().put(attr.getSchema(), attr) != null; + } + + public boolean remove(final String plainSchema) { + return plainAttrs().put(plainSchema, null) != null; + } +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jTypeExtension.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractTypeExtension.java similarity index 63% rename from core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jTypeExtension.java rename to core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractTypeExtension.java index 07108c04b0a..7316c48298c 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jTypeExtension.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractTypeExtension.java @@ -16,29 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.core.persistence.neo4j.entity.group; +package org.apache.syncope.core.persistence.neo4j.entity; import java.util.ArrayList; import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; -import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; -import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; -import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyType; -import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass; -import org.springframework.data.neo4j.core.schema.Node; +import org.apache.syncope.core.persistence.api.entity.TypeExtension; import org.springframework.data.neo4j.core.schema.Relationship; -@Node(Neo4jTypeExtension.NODE) -public class Neo4jTypeExtension extends AbstractGeneratedKeyNode implements TypeExtension { +public abstract class AbstractTypeExtension extends AbstractGeneratedKeyNode implements TypeExtension { - private static final long serialVersionUID = -8367626793791263551L; - - public static final String NODE = "TypeExtension"; - - @Relationship(type = Neo4jGroup.GROUP_TYPE_EXTENSION_REL, direction = Relationship.Direction.OUTGOING) - private Neo4jGroup group; + private static final long serialVersionUID = 6614200749937020514L; @Relationship(direction = Relationship.Direction.OUTGOING) private Neo4jAnyType anyType; @@ -46,17 +35,6 @@ public class Neo4jTypeExtension extends AbstractGeneratedKeyNode implements Type @Relationship(direction = Relationship.Direction.OUTGOING) private List auxClasses = new ArrayList<>(); - @Override - public Group getGroup() { - return group; - } - - @Override - public void setGroup(final Group group) { - checkType(group, Neo4jGroup.class); - this.group = (Neo4jGroup) group; - } - @Override public AnyType getAnyType() { return anyType; diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java index 5cf942a10f7..d1f6a72e2ec 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java @@ -43,6 +43,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.api.entity.Remediation; import org.apache.syncope.core.persistence.api.entity.Report; import org.apache.syncope.core.persistence.api.entity.ReportExec; @@ -64,7 +65,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.keymaster.ConfParam; import org.apache.syncope.core.persistence.api.entity.keymaster.DomainEntity; import org.apache.syncope.core.persistence.api.entity.keymaster.NetworkServiceEntity; @@ -111,7 +112,7 @@ import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject; import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGRelationship; import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup; -import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jTypeExtension; +import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroupTypeExtension; import org.apache.syncope.core.persistence.neo4j.entity.keymaster.Neo4jConfParam; import org.apache.syncope.core.persistence.neo4j.entity.keymaster.Neo4jDomain; import org.apache.syncope.core.persistence.neo4j.entity.keymaster.Neo4jNetworkService; @@ -185,10 +186,12 @@ public E newEntity(final Class reference) { result = (E) new Neo4jUser(); } else if (reference.equals(Group.class)) { result = (E) new Neo4jGroup(); - } else if (reference.equals(TypeExtension.class)) { - result = (E) new Neo4jTypeExtension(); + } else if (reference.equals(GroupTypeExtension.class)) { + result = (E) new Neo4jGroupTypeExtension(); } else if (reference.equals(RelationshipType.class)) { result = (E) new Neo4jRelationshipType(); + } else if (reference.equals(RelationshipTypeExtension.class)) { + result = (E) new Neo4jRelationshipTypeExtension(); } else if (reference.equals(ARelationship.class)) { result = (E) new Neo4jARelationship(); } else if (reference.equals(GRelationship.class)) { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipType.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipType.java index 033aaba943e..16db97e6bd2 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipType.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipType.java @@ -19,8 +19,12 @@ package org.apache.syncope.core.persistence.neo4j.entity; import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.common.validation.RelationshipTypeCheck; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @@ -37,6 +41,8 @@ public class Neo4jRelationshipType extends AbstractProvidedKeyNode implements Re public static final String RIGHT_END_ANYTYPE = "RELATIONSHIPTYPE_RIGHT_END_ANYTYPE"; + public static final String RELATIONSHIP_TYPE_EXTENSION_REL = "RELATIONSHIP_TYPE_EXTENSION"; + private String description; @NotNull @@ -47,6 +53,9 @@ public class Neo4jRelationshipType extends AbstractProvidedKeyNode implements Re @Relationship(type = RIGHT_END_ANYTYPE, direction = Relationship.Direction.OUTGOING) private Neo4jAnyType rightEndAnyType; + @Relationship(type = RELATIONSHIP_TYPE_EXTENSION_REL, direction = Relationship.Direction.INCOMING) + private List typeExtensions = new ArrayList<>(); + @Override public String getDescription() { return description; @@ -78,4 +87,22 @@ public void setRightEndAnyType(final AnyType anyType) { checkType(anyType, Neo4jAnyType.class); this.rightEndAnyType = (Neo4jAnyType) anyType; } + + @Override + public boolean add(final RelationshipTypeExtension typeExtension) { + checkType(typeExtension, Neo4jRelationshipTypeExtension.class); + return typeExtensions.add((Neo4jRelationshipTypeExtension) typeExtension); + } + + @Override + public Optional getTypeExtension(final AnyType anyType) { + return typeExtensions.stream(). + filter(typeExtension -> typeExtension.getAnyType().equals(anyType)). + findFirst(); + } + + @Override + public List getTypeExtensions() { + return typeExtensions; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipTypeExtension.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipTypeExtension.java new file mode 100644 index 00000000000..1cce9608f80 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jRelationshipTypeExtension.java @@ -0,0 +1,48 @@ +/* + * 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.core.persistence.neo4j.entity; + +import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; + +@Node(Neo4jRelationshipTypeExtension.NODE) +public class Neo4jRelationshipTypeExtension extends AbstractTypeExtension implements RelationshipTypeExtension { + + private static final long serialVersionUID = 7566998432028027512L; + + public static final String NODE = "RelationshipTypeExtension"; + + @Relationship( + type = Neo4jRelationshipType.RELATIONSHIP_TYPE_EXTENSION_REL, + direction = Relationship.Direction.OUTGOING) + private Neo4jRelationshipType group; + + @Override + public RelationshipType getRelationshipType() { + return group; + } + + @Override + public void setRelationshipType(final RelationshipType relationshipType) { + checkType(relationshipType, Neo4jRelationshipType.class); + this.group = (Neo4jRelationshipType) relationshipType; + } +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAMembership.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAMembership.java index 719b31a51ef..7e11a1c10a5 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAMembership.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAMembership.java @@ -18,9 +18,7 @@ */ package org.apache.syncope.core.persistence.neo4j.entity.anyobject; -import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.PlainAttr; @@ -94,30 +92,6 @@ protected Map plainAttrs() { return plainAttrs; } - @Override - public List getPlainAttrs() { - return plainAttrs.entrySet().stream(). - filter(e -> e.getValue() != null). - sorted(Comparator.comparing(Map.Entry::getKey)). - map(Map.Entry::getValue).toList(); - } - - @Override - public Optional getPlainAttr(final String plainSchema) { - return Optional.ofNullable(plainAttrs.get(plainSchema)); - } - - @Override - public boolean add(final PlainAttr attr) { - return getKey().equals(attr.getMembership()) - && plainAttrs.put(attr.getSchema(), attr) != null; - } - - @Override - public boolean remove(final String plainSchema) { - return plainAttrs.put(plainSchema, null) != null; - } - @PostLoad public void postLoad() { this.aMembershipType = new AMembershipType(leftEnd.getType()); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jARelationship.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jARelationship.java index 135cb8ac3c2..61910576bfe 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jARelationship.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jARelationship.java @@ -19,20 +19,24 @@ package org.apache.syncope.core.persistence.neo4j.entity.anyobject; import jakarta.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.common.entity.AMembershipType; import org.apache.syncope.core.persistence.common.entity.UMembershipType; import org.apache.syncope.core.persistence.common.validation.RelationshipCheck; -import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRelationshipType; +import org.springframework.data.neo4j.core.schema.CompositeProperty; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node(Neo4jARelationship.NODE) @RelationshipCheck -public class Neo4jARelationship extends AbstractGeneratedKeyNode implements ARelationship { +public class Neo4jARelationship extends AbstractRelationship implements ARelationship { private static final long serialVersionUID = 2778494939240083204L; @@ -52,6 +56,9 @@ public class Neo4jARelationship extends AbstractGeneratedKeyNode implements ARel @Relationship(type = DEST_REL, direction = Relationship.Direction.OUTGOING) private Neo4jAnyObject rightEnd; + @CompositeProperty(converterRef = "plainAttrsConverter") + private Map plainAttrs = new HashMap<>(); + @Override public RelationshipType getType() { return type; @@ -89,4 +96,9 @@ public void setRightEnd(final AnyObject rightEnd) { checkType(rightEnd, Neo4jAnyObject.class); this.rightEnd = (Neo4jAnyObject) rightEnd; } + + @Override + protected Map plainAttrs() { + return plainAttrs; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java index 73890ab1847..a389b5fce68 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java @@ -33,6 +33,7 @@ import org.apache.syncope.core.persistence.common.validation.AnyObjectCheck; import org.apache.syncope.core.persistence.common.validation.AttributableCheck; import org.apache.syncope.core.persistence.neo4j.entity.AbstractGroupableRelatable; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyType; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jExternalResource; @@ -149,7 +150,13 @@ public boolean add(final ARelationship relationship) { } @Override - public List getRelationships() { + public boolean remove(final org.apache.syncope.core.persistence.api.entity.Relationship relationship) { + checkType(relationship, Neo4jARelationship.class); + return relationships.remove((Neo4jARelationship) relationship); + } + + @Override + protected List> relationships() { return relationships; } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGRelationship.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGRelationship.java index dc501da91eb..fd67f6f18c6 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGRelationship.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGRelationship.java @@ -19,6 +19,9 @@ package org.apache.syncope.core.persistence.neo4j.entity.group; import jakarta.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; @@ -26,15 +29,16 @@ import org.apache.syncope.core.persistence.common.entity.AMembershipType; import org.apache.syncope.core.persistence.common.entity.UMembershipType; import org.apache.syncope.core.persistence.common.validation.RelationshipCheck; -import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRelationshipType; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject; +import org.springframework.data.neo4j.core.schema.CompositeProperty; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node(Neo4jGRelationship.NODE) @RelationshipCheck -public class Neo4jGRelationship extends AbstractGeneratedKeyNode implements GRelationship { +public class Neo4jGRelationship extends AbstractRelationship implements GRelationship { private static final long serialVersionUID = 2778494939240083204L; @@ -54,6 +58,9 @@ public class Neo4jGRelationship extends AbstractGeneratedKeyNode implements GRel @Relationship(type = DEST_REL, direction = Relationship.Direction.OUTGOING) private Neo4jAnyObject rightEnd; + @CompositeProperty(converterRef = "plainAttrsConverter") + protected Map plainAttrs = new HashMap<>(); + @Override public RelationshipType getType() { return type; @@ -91,4 +98,9 @@ public void setRightEnd(final AnyObject rightEnd) { checkType(rightEnd, Neo4jAnyObject.class); this.rightEnd = (Neo4jAnyObject) rightEnd; } + + @Override + protected Map plainAttrs() { + return plainAttrs; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java index ae89c503c04..a51798ef9b2 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java @@ -31,14 +31,16 @@ import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.common.validation.AttributableCheck; import org.apache.syncope.core.persistence.common.validation.GroupCheck; import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelatable; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jExternalResource; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jADynGroupMembership; @@ -97,7 +99,7 @@ public class Neo4jGroup private List aDynMemberships = new ArrayList<>(); @Relationship(type = GROUP_TYPE_EXTENSION_REL, direction = Relationship.Direction.INCOMING) - private List typeExtensions = new ArrayList<>(); + private List typeExtensions = new ArrayList<>(); @Relationship(type = Neo4jGRelationship.SOURCE_REL, direction = Relationship.Direction.INCOMING) protected List relationships = new ArrayList<>(); @@ -174,7 +176,7 @@ public void setUDynMembership(final UDynGroupMembership uDynMembership) { @Override public boolean add(final AnyTypeClass auxClass) { checkType(auxClass, Neo4jAnyTypeClass.class); - return auxClasses.contains((Neo4jAnyTypeClass) auxClass) || this.auxClasses.add((Neo4jAnyTypeClass) auxClass); + return auxClasses.contains((Neo4jAnyTypeClass) auxClass) || auxClasses.add((Neo4jAnyTypeClass) auxClass); } @Override @@ -185,7 +187,7 @@ public List getAuxClasses() { @Override public boolean add(final ADynGroupMembership dynGroupMembership) { checkType(dynGroupMembership, Neo4jADynGroupMembership.class); - return this.aDynMemberships.add((Neo4jADynGroupMembership) dynGroupMembership); + return aDynMemberships.add((Neo4jADynGroupMembership) dynGroupMembership); } @Override @@ -201,31 +203,37 @@ public List getADynMemberships() { } @Override - public boolean add(final TypeExtension typeExtension) { - checkType(typeExtension, Neo4jTypeExtension.class); - return this.typeExtensions.add((Neo4jTypeExtension) typeExtension); + public boolean add(final GroupTypeExtension typeExtension) { + checkType(typeExtension, Neo4jGroupTypeExtension.class); + return typeExtensions.add((Neo4jGroupTypeExtension) typeExtension); } @Override - public Optional getTypeExtension(final AnyType anyType) { + public Optional getTypeExtension(final AnyType anyType) { return typeExtensions.stream(). filter(typeExtension -> typeExtension.getAnyType().equals(anyType)). findFirst(); } @Override - public List getTypeExtensions() { + public List getTypeExtensions() { return typeExtensions; } @Override public boolean add(final GRelationship relationship) { checkType(relationship, Neo4jGRelationship.class); - return this.relationships.add((Neo4jGRelationship) relationship); + return relationships.add((Neo4jGRelationship) relationship); } @Override - public List getRelationships() { + public boolean remove(final org.apache.syncope.core.persistence.api.entity.Relationship relationship) { + checkType(relationship, Neo4jGRelationship.class); + return relationships.remove((Neo4jGRelationship) relationship); + } + + @Override + protected List> relationships() { return relationships; } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroupTypeExtension.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroupTypeExtension.java new file mode 100644 index 00000000000..43231916493 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroupTypeExtension.java @@ -0,0 +1,47 @@ +/* + * 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.core.persistence.neo4j.entity.group; + +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractTypeExtension; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; + +@Node(Neo4jGroupTypeExtension.NODE) +public class Neo4jGroupTypeExtension extends AbstractTypeExtension implements GroupTypeExtension { + + private static final long serialVersionUID = -8367626793791263551L; + + public static final String NODE = "GroupTypeExtension"; + + @Relationship(type = Neo4jGroup.GROUP_TYPE_EXTENSION_REL, direction = Relationship.Direction.OUTGOING) + private Neo4jGroup group; + + @Override + public Group getGroup() { + return group; + } + + @Override + public void setGroup(final Group group) { + checkType(group, Neo4jGroup.class); + this.group = (Neo4jGroup) group; + } +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUMembership.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUMembership.java index 26196df6f3e..e8138e12cf3 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUMembership.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUMembership.java @@ -18,11 +18,8 @@ */ package org.apache.syncope.core.persistence.neo4j.entity.user; -import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Optional; import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.group.Group; @@ -87,28 +84,4 @@ public void setRightEnd(final Group rightEnd) { protected Map plainAttrs() { return plainAttrs; } - - @Override - public List getPlainAttrs() { - return plainAttrs.entrySet().stream(). - filter(e -> e.getValue() != null). - sorted(Comparator.comparing(Map.Entry::getKey)). - map(Map.Entry::getValue).toList(); - } - - @Override - public Optional getPlainAttr(final String plainSchema) { - return Optional.ofNullable(plainAttrs.get(plainSchema)); - } - - @Override - public boolean add(final PlainAttr attr) { - return getKey().equals(attr.getMembership()) - && plainAttrs.put(attr.getSchema(), attr) != null; - } - - @Override - public boolean remove(final String plainSchema) { - return plainAttrs.put(plainSchema, null) != null; - } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jURelationship.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jURelationship.java index b66b251e008..ab5e487aa7a 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jURelationship.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jURelationship.java @@ -19,6 +19,9 @@ package org.apache.syncope.core.persistence.neo4j.entity.user; import jakarta.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.user.URelationship; @@ -26,15 +29,16 @@ import org.apache.syncope.core.persistence.common.entity.AMembershipType; import org.apache.syncope.core.persistence.common.entity.UMembershipType; import org.apache.syncope.core.persistence.common.validation.RelationshipCheck; -import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRelationshipType; import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject; +import org.springframework.data.neo4j.core.schema.CompositeProperty; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node(Neo4jURelationship.NODE) @RelationshipCheck -public class Neo4jURelationship extends AbstractGeneratedKeyNode implements URelationship { +public class Neo4jURelationship extends AbstractRelationship implements URelationship { private static final long serialVersionUID = 2778494939240083204L; @@ -54,6 +58,9 @@ public class Neo4jURelationship extends AbstractGeneratedKeyNode implements URel @Relationship(type = DEST_REL, direction = Relationship.Direction.OUTGOING) private Neo4jAnyObject rightEnd; + @CompositeProperty(converterRef = "plainAttrsConverter") + private Map plainAttrs = new HashMap<>(); + @Override public RelationshipType getType() { return type; @@ -91,4 +98,9 @@ public void setRightEnd(final AnyObject rightEnd) { checkType(rightEnd, Neo4jAnyObject.class); this.rightEnd = (Neo4jAnyObject) rightEnd; } + + @Override + protected Map plainAttrs() { + return plainAttrs; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java index 3b8dc2536c0..41d152dbacc 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java @@ -37,6 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.Role; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion; import org.apache.syncope.core.persistence.api.entity.user.UMembership; @@ -44,6 +45,7 @@ import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.common.validation.AttributableCheck; import org.apache.syncope.core.persistence.neo4j.entity.AbstractGroupableRelatable; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractRelationship; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jExternalResource; import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRole; @@ -406,24 +408,30 @@ public List getAuxClasses() { @Override public boolean add(final URelationship relationship) { checkType(relationship, Neo4jURelationship.class); - return this.relationships.add((Neo4jURelationship) relationship); + return relationships.add((Neo4jURelationship) relationship); } @Override - public List getRelationships() { + public boolean remove(final org.apache.syncope.core.persistence.api.entity.Relationship relationship) { + checkType(relationship, Neo4jURelationship.class); + return relationships.remove((Neo4jURelationship) relationship); + } + + @Override + protected List> relationships() { return relationships; } @Override public boolean add(final UMembership membership) { checkType(membership, Neo4jUMembership.class); - return this.memberships.add((Neo4jUMembership) membership); + return memberships.add((Neo4jUMembership) membership); } @Override public boolean remove(final UMembership membership) { checkType(membership, Neo4jUMembership.class); - return this.memberships.remove((Neo4jUMembership) membership); + return memberships.remove((Neo4jUMembership) membership); } @Override diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java index af058ee7b2d..8286c5f2b78 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java @@ -36,7 +36,6 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO; import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; -import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.neo4j.AbstractTest; import org.apache.syncope.core.spring.security.PasswordGenerator; @@ -149,13 +148,6 @@ public void findByUsername() { assertTrue(userDAO.findByUsername("user6").isEmpty()); } - @Test - public void findMembership() { - UMembership memb = userDAO.findMembership("3d5e91f6-305e-45f9-ad30-4897d3d43bd9"); - assertNotNull(memb); - assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", memb.getLeftEnd().getKey()); - } - @Test public void save() { User user = entityFactory.newEntity(User.class); diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java index ef74532e6d3..aab29f0cc2d 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java @@ -48,7 +48,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.GRelationship; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -185,7 +185,7 @@ public void create() { group.setRealm(realmDAO.getRoot()); group.setName("new"); - TypeExtension typeExt = entityFactory.newEntity(TypeExtension.class); + GroupTypeExtension typeExt = entityFactory.newEntity(GroupTypeExtension.class); typeExt.setAnyType(anyTypeDAO.getUser()); typeExt.add(anyTypeClassDAO.findById("csv").orElseThrow()); typeExt.add(anyTypeClassDAO.findById("other").orElseThrow()); diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java index 9f596eb4025..78eb585fe8f 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java @@ -126,7 +126,7 @@ public void ships() { assertEquals(1, user.getRelationships().size()); assertEquals("fc6dbc3a-6c07-4965-8781-921e7401a4a5", user.getRelationships().getFirst().getRightEnd().getKey()); - user.getRelationships().removeFirst(); + user.remove(user.getRelationships().getFirst()); URelationship newR = entityFactory.newEntity(URelationship.class); newR.setType(relationshipTypeDAO.findById("neighborhood").orElseThrow()); diff --git a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml index 151893a38ba..abf56d73ec0 100644 --- a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml @@ -395,10 +395,10 @@ under the License. creator="admin" lastModifier="admin" creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/> - - - - + + + + @@ -431,11 +431,11 @@ under the License. creator="admin" lastModifier="admin" creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/> - - - - - + + + + + diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java index f9863946a9d..395e2c82d41 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java @@ -24,6 +24,8 @@ import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.Membership; import org.apache.syncope.core.persistence.api.entity.Realm; +import org.apache.syncope.core.persistence.api.entity.Relatable; +import org.apache.syncope.core.persistence.api.entity.Relationship; public interface DerAttrHandler { @@ -80,4 +82,24 @@ public interface DerAttrHandler { * @return derived attribute values */ Map getValues(Groupable any, Membership membership); + + /** + * Calculates derived attribute value associated to the given any, for the given relationship and + * derived schema. + * + * @param any any object + * @param relationship relationship + * @param schema derived schema + * @return derived attribute value + */ + String getValue(Any any, Relationship relationship, DerSchema schema); + + /** + * Calculates derived attributes values associated to the given any, for the given relationship. + * + * @param any any object + * @param relationship relationship + * @return derived attribute values + */ + Map getValues(Relatable any, Relationship relationship); } diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java index 6f0e7b67b31..9124a383a44 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java @@ -24,14 +24,14 @@ import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.TypeExtensionTO; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.provisioning.api.PropagationByResource; public interface GroupDataBinder { GroupTO getGroupTO(String key); - TypeExtensionTO getTypeExtensionTO(TypeExtension typeExt); + TypeExtensionTO getTypeExtensionTO(GroupTypeExtension typeExt); GroupTO getGroupTO(Group group, boolean details); diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java index edd822dc7ae..8d56f41db6c 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java @@ -31,6 +31,8 @@ import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.Membership; import org.apache.syncope.core.persistence.api.entity.Realm; +import org.apache.syncope.core.persistence.api.entity.Relatable; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.provisioning.api.DerAttrHandler; import org.apache.syncope.core.provisioning.api.jexl.JexlContextBuilder; import org.apache.syncope.core.provisioning.api.jexl.JexlTools; @@ -82,7 +84,7 @@ public String getValue(final Realm realm, final DerSchema schema) { @Override public String getValue(final Any any, final DerSchema schema) { - if (!anyUtilsFactory.getInstance(any).dao().findAllowedSchemas(any, DerSchema.class).forSelfContains(schema)) { + if (!anyUtilsFactory.getInstance(any).dao().findAllowedSchemas(any, DerSchema.class).selfContains(schema)) { LOG.debug("{} not allowed for {}", schema, any); return null; } @@ -93,7 +95,7 @@ public String getValue(final Any any, final DerSchema schema) { @Override public String getValue(final Any any, final Membership membership, final DerSchema schema) { if (!anyUtilsFactory.getInstance(any).dao(). - findAllowedSchemas(any, DerSchema.class).getForMembership(membership.getRightEnd()).contains(schema)) { + findAllowedSchemas(any, DerSchema.class).membershipsContains(membership.getRightEnd(), schema)) { LOG.debug("{} not allowed for {}", schema, any); return null; @@ -114,18 +116,19 @@ public Map getValues(final Realm realm) { public Map getValues(final Any any) { return getValues( any, - anyUtilsFactory.getInstance(any).dao().findAllowedSchemas(any, DerSchema.class).getForSelf()); + anyUtilsFactory.getInstance(any).dao().findAllowedSchemas(any, DerSchema.class).self()); } - protected Map getValues( - final Groupable groupable, final Membership membership, final Set schemas) { - + @Override + public Map getValues(final Groupable any, final Membership membership) { + Set schemas = anyUtilsFactory.getInstance(any).dao(). + findAllowedSchemas(any, DerSchema.class).membership(membership.getRightEnd()); Map result = new HashMap<>(schemas.size()); schemas.forEach(schema -> { JexlContext jexlContext = new JexlContextBuilder(). - plainAttrs(groupable.getPlainAttrs(membership)). - fields(groupable). + plainAttrs(any.getPlainAttrs(membership)). + fields(any). build(); result.put(schema, jexlTools.evaluateExpression(schema.getExpression(), jexlContext).toString()); @@ -135,11 +138,32 @@ protected Map getValues( } @Override - public Map getValues(final Groupable any, final Membership membership) { - return getValues( - any, - membership, - anyUtilsFactory.getInstance(any).dao(). - findAllowedSchemas(any, DerSchema.class).getForMembership(membership.getRightEnd())); + public String getValue(final Any any, final Relationship relationship, final DerSchema schema) { + if (!anyUtilsFactory.getInstance(any).dao(). + findAllowedSchemas(any, DerSchema.class).relationshipTypesContains(relationship.getType(), schema)) { + + LOG.debug("{} not allowed for {}", schema, any); + return null; + } + + return getValues(any, Set.of(schema)).get(schema); + } + + @Override + public Map getValues(final Relatable any, final Relationship relationship) { + Set schemas = anyUtilsFactory.getInstance(any).dao(). + findAllowedSchemas(any, DerSchema.class).relationshipType(relationship.getType()); + Map result = new HashMap<>(schemas.size()); + + schemas.forEach(schema -> { + JexlContext jexlContext = new JexlContextBuilder(). + plainAttrs(any.getPlainAttrs(relationship)). + fields(any). + build(); + + result.put(schema, jexlTools.evaluateExpression(schema.getExpression(), jexlContext).toString()); + }); + + return result; } } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java index 7653817aca6..6044b3f2eab 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java @@ -587,6 +587,7 @@ public NotificationManager notificationManager( final AnySearchDAO anySearchDAO, final AnyMatchDAO anyMatchDAO, final TaskDAO taskDAO, + final RelationshipTypeDAO relationshipTypeDAO, final UserDataBinder userDataBinder, final GroupDataBinder groupDataBinder, final AnyObjectDataBinder anyObjectDataBinder, @@ -604,6 +605,7 @@ public NotificationManager notificationManager( anySearchDAO, anyMatchDAO, taskDAO, + relationshipTypeDAO, derAttrHandler, userDataBinder, groupDataBinder, @@ -991,9 +993,10 @@ public RealmDataBinder realmDataBinder( @Bean public RelationshipTypeDataBinder relationshipTypeDataBinder( final AnyTypeDAO anyTypeDAO, + final AnyTypeClassDAO anyTypeClassDAO, final EntityFactory entityFactory) { - return new RelationshipTypeDataBinderImpl(anyTypeDAO, entityFactory); + return new RelationshipTypeDataBinderImpl(anyTypeDAO, anyTypeClassDAO, entityFactory); } @ConditionalOnMissingBean 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 c14fcec373d..ccb18c197e1 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 @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -36,6 +37,7 @@ import org.apache.syncope.common.lib.request.AnyCR; import org.apache.syncope.common.lib.request.AnyUR; import org.apache.syncope.common.lib.request.AttrPatch; +import org.apache.syncope.common.lib.request.MembershipUR; import org.apache.syncope.common.lib.request.RelationshipUR; import org.apache.syncope.common.lib.request.StringPatchItem; import org.apache.syncope.common.lib.to.AnyTO; @@ -47,6 +49,7 @@ 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.ResourceOperation; import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager; import org.apache.syncope.core.persistence.api.dao.AllowedSchemas; import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; @@ -71,6 +74,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainAttr; import org.apache.syncope.core.persistence.api.entity.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Relatable; +import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; @@ -81,6 +85,7 @@ import org.apache.syncope.core.provisioning.api.IntAttrNameParser; import org.apache.syncope.core.provisioning.api.MappingManager; import org.apache.syncope.core.provisioning.api.PlainAttrGetter; +import org.apache.syncope.core.provisioning.api.PropagationByResource; import org.apache.syncope.core.provisioning.api.jexl.JexlTools; import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher; import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils; @@ -114,11 +119,13 @@ protected static void fillTO( } protected static RelationshipTO getRelationshipTO( + final Collection plainAttrs, + final Map derAttrs, final String relationshipType, final RelationshipTO.End end, final Any otherEnd) { - return new RelationshipTO.Builder(relationshipType, end).otherEnd( + RelationshipTO relationshipTO = new RelationshipTO.Builder(relationshipType, end).otherEnd( otherEnd.getType().getKey(), otherEnd.getKey(), otherEnd instanceof User user @@ -127,6 +134,14 @@ protected static RelationshipTO getRelationshipTO( ? group.getName() : ((AnyObject) otherEnd).getName()). build(); + + plainAttrs.forEach(plainAttr -> relationshipTO.getPlainAttrs(). + add(new Attr.Builder(plainAttr.getSchema()).values(plainAttr.getValuesAsStrings()).build())); + + derAttrs.forEach((schema, value) -> relationshipTO.getDerAttrs(). + add(new Attr.Builder(schema.getKey()).value(value).build())); + + return relationshipTO; } protected static MembershipTO getMembershipTO( @@ -306,10 +321,10 @@ protected SyncopeClientException checkMandatory(final Any any, final AnyUtils an // Check if there is some mandatory schema defined for which no value has been provided AllowedSchemas allowedPlainSchemas = anyUtils.dao().findAllowedSchemas(any, PlainSchema.class); - allowedPlainSchemas.getForSelf().forEach(schema -> checkMandatory( + allowedPlainSchemas.self().forEach(schema -> checkMandatory( schema, any.getPlainAttr(schema.getKey()).orElse(null), any, reqValMissing)); if (any instanceof Groupable groupable) { - allowedPlainSchemas.getForMemberships().forEach((group, schemas) -> { + allowedPlainSchemas.memberships().forEach((group, schemas) -> { Membership membership = groupable.getMembership(group.getKey()).orElse(null); schemas.forEach(schema -> checkMandatory( schema, @@ -318,6 +333,16 @@ protected SyncopeClientException checkMandatory(final Any any, final AnyUtils an reqValMissing)); }); } + if (any instanceof Relatable relatable) { + allowedPlainSchemas.relationshipTypes().forEach((relationshipType, schemas) -> { + List> rels = relatable.getRelationships(relationshipType.getKey()); + rels.forEach(rel -> schemas.forEach(schema -> checkMandatory( + schema, + relatable.getPlainAttr(schema.getKey(), rel).orElse(null), + any, + reqValMissing))); + }); + } return reqValMissing; } @@ -370,8 +395,9 @@ protected void processAttrPatch( @SuppressWarnings({ "unchecked", "rawtypes" }) protected void fill( final AnyTO anyTO, - final Any any, + final Relatable any, final AnyUR anyUR, + final PropagationByResource propByRes, final AnyUtils anyUtils, final SyncopeClientCompositeException scce) { @@ -393,7 +419,76 @@ protected void fill( AnyTypeClass.class.getSimpleName(), patch.getValue())); } - // 2. resources + // 2. relationships + Set> relationships = new HashSet<>(); + for (RelationshipUR patch : anyUR.getRelationships().stream(). + filter(patch -> patch.getType() != null + && patch.getOtherEndType() != null && patch.getOtherEndKey() != null).toList()) { + + RelationshipType relationshipType = relationshipTypeDAO.findById(patch.getType()).orElse(null); + if (relationshipType == null) { + LOG.debug("Ignoring invalid relationship type {}", patch.getType()); + } else { + switch (patch.getOperation()) { + case DELETE -> { + any.getRelationship(relationshipType, patch.getOtherEndKey()).ifPresentOrElse( + relationship -> { + anyUtils.remove(any, relationship); + + propByRes.addAll( + ResourceOperation.UPDATE, + anyObjectDAO.findAllResourceKeys(relationship.getRightEnd().getKey())); + }, + () -> LOG.debug("No relationship ({},{},{}) was found, nothing to delete", + anyTO.getKey(), relationshipType.getKey(), patch.getOtherEndKey())); + } + + case ADD_REPLACE -> { + if (AnyTypeKind.USER.name().equals(patch.getOtherEndType()) + || AnyTypeKind.GROUP.name().equals(patch.getOtherEndType())) { + + SyncopeClientException invalidAnyType = + SyncopeClientException.build(ClientExceptionType.InvalidAnyType); + invalidAnyType.getElements().add(AnyType.class.getSimpleName() + + " not allowed for relationship: " + patch.getOtherEndType()); + scce.addException(invalidAnyType); + } else { + AnyObject otherEnd = anyObjectDAO.findById(patch.getOtherEndKey()).orElse(null); + if (otherEnd == null) { + LOG.debug("Ignoring invalid any object {}", patch.getOtherEndKey()); + } else if (relationships.contains(Pair.of(relationshipType.getKey(), otherEnd.getKey()))) { + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() + + " in relationship " + relationshipType.getKey()); + scce.addException(assigned); + } else { + relationships.add(Pair.of(relationshipType.getKey(), otherEnd.getKey())); + + Relationship relationship = any.getRelationship( + relationshipType, patch.getOtherEndKey()).orElse(null); + if (relationship == null) { + relationship = anyUtils.add(any, relationshipType, otherEnd); + } else { + any.getPlainAttrs(relationship).forEach(any::remove); + } + + // relationship attributes + relationshipPlainAttrsOnUpdate(patch.getPlainAttrs(), anyTO, any, relationship, scce); + + propByRes.addAll( + ResourceOperation.UPDATE, anyObjectDAO.findAllResourceKeys(otherEnd.getKey())); + } + } + } + + default -> { + } + } + } + } + + // 3. resources for (StringPatchItem patch : anyUR.getResources()) { resourceDAO.findById(patch.getValue()).ifPresentOrElse( resource -> { @@ -414,7 +509,7 @@ protected void fill( Set resources = anyUtils.getAllResources(any); SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); - // 3. plain attributes + // 4. attributes anyUR.getPlainAttrs().stream().filter(patch -> patch.getAttr() != null). forEach(patch -> getPlainSchema(patch.getAttr().getSchema()).ifPresentOrElse( schema -> { @@ -446,66 +541,72 @@ protected void fill( if (!reqValMissing.isEmpty()) { scce.addException(reqValMissing); } + } - // relationships - Set> relationships = new HashSet<>(); - for (RelationshipUR patch : anyUR.getRelationships().stream(). - filter(patch -> patch.getRelationshipTO() != null).toList()) { + protected void memberships( + final Set memberships, + final AnyTO anyTO, + final Groupable any, + final Consumer groupProcessor, + final PropagationByResource propByRes, + final AnyUtils anyUtils, + final SyncopeClientCompositeException scce) { - RelationshipType relationshipType = relationshipTypeDAO.findById(patch.getRelationshipTO().getType()). - orElse(null); - if (relationshipType == null) { - LOG.debug("Ignoring invalid relationship type {}", patch.getRelationshipTO().getType()); - } else { - anyUtils.removeRelationship( - (Relatable) any, - relationshipType, - patch.getRelationshipTO().getOtherEndKey()); - - if (patch.getOperation() == PatchOperation.ADD_REPLACE) { - if (StringUtils.isBlank(patch.getRelationshipTO().getOtherEndType()) - || AnyTypeKind.USER.name().equals(patch.getRelationshipTO().getOtherEndType()) - || AnyTypeKind.GROUP.name().equals(patch.getRelationshipTO().getOtherEndType())) { - - SyncopeClientException invalidAnyType = - SyncopeClientException.build(ClientExceptionType.InvalidAnyType); - invalidAnyType.getElements().add(AnyType.class.getSimpleName() - + " not allowed for relationship: " + patch.getRelationshipTO().getOtherEndType()); - scce.addException(invalidAnyType); + Set groups = new HashSet<>(); + memberships.stream().filter(patch -> patch.getGroup() != null).forEach(patch -> { + switch (patch.getOperation()) { + case DELETE -> { + any.getMembership(patch.getGroup()).ifPresentOrElse( + membership -> { + anyUtils.remove(any, membership); + + propByRes.addAll( + ResourceOperation.UPDATE, + groupDAO.findAllResourceKeys(membership.getRightEnd().getKey())); + }, + () -> LOG.debug("No membership ({},{}) was found, nothing to delete", + anyTO.getKey(), patch.getGroup())); + } + + case ADD_REPLACE -> { + Group group = groupDAO.findById(patch.getGroup()).orElse(null); + if (group == null) { + LOG.debug("Ignoring invalid group {}", patch.getGroup()); + } else if (groups.contains(group.getKey())) { + LOG.error("Multiple patches for group {} of {} were found", group, any); + + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidMembership); + assigned.getElements().add("Multiple patches for group " + group.getName() + " were found"); + scce.addException(assigned); } else { - AnyObject otherEnd = anyObjectDAO.findById(patch.getRelationshipTO().getOtherEndKey()). - orElse(null); - if (otherEnd == null) { - LOG.debug("Ignoring invalid any object {}", patch.getRelationshipTO().getOtherEndKey()); - } else if (relationships.contains( - Pair.of(otherEnd.getKey(), patch.getRelationshipTO().getType()))) { - - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - assigned.getElements().add("Group was already in relationship " - + patch.getRelationshipTO().getType() + " with " - + otherEnd.getType().getKey() + " " + otherEnd.getName()); - scce.addException(assigned); - } else if (patch.getRelationshipTO().getEnd() == RelationshipTO.End.RIGHT) { - SyncopeClientException noRight = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - noRight.getElements().add( - "Relationships shall be created or updated only from their left end"); - scce.addException(noRight); - } else { - relationships.add(Pair.of(otherEnd.getKey(), patch.getRelationshipTO().getType())); + groups.add(group.getKey()); - anyUtils.addRelationship((Relatable) any, relationshipType, otherEnd); + Membership membership = any.getMembership(patch.getGroup()).orElse(null); + if (membership == null) { + membership = anyUtils.add(any, group); + } else { + any.getPlainAttrs(membership).forEach(any::remove); } + + // membership attributes + membershipPlainAttrsOnUpdate(patch.getPlainAttrs(), anyTO, any, membership, scce); + + propByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey())); + + groupProcessor.accept(group); } } + + default -> { + } } - } + }); } protected void fill( final AnyTO anyTO, - final Any any, + final Relatable any, final AnyCR anyCR, final AnyUtils anyUtils, final SyncopeClientCompositeException scce) { @@ -523,7 +624,51 @@ protected void fill( } }); - // 1. attributes + // 1. relationships + Set> relationships = new HashSet<>(); + anyCR.getRelationships().forEach(relationshipTO -> { + if (StringUtils.isBlank(relationshipTO.getOtherEndType()) + || AnyTypeKind.USER.name().equals(relationshipTO.getOtherEndType()) + || AnyTypeKind.GROUP.name().equals(relationshipTO.getOtherEndType())) { + + SyncopeClientException invalidAnyType = + SyncopeClientException.build(ClientExceptionType.InvalidAnyType); + invalidAnyType.getElements().add(AnyType.class.getSimpleName() + + " not allowed for relationship: " + relationshipTO.getOtherEndType()); + scce.addException(invalidAnyType); + } else { + RelationshipType relationshipType = relationshipTypeDAO.findById(relationshipTO.getType()).orElse(null); + AnyObject otherEnd = anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null); + if (relationshipType == null) { + LOG.debug("Ignoring invalid relationship type {}", relationshipTO.getType()); + } else if (otherEnd == null) { + LOG.debug("Ignoring invalid anyObject {}", relationshipTO.getOtherEndKey()); + } else if (!relationshipTO.getOtherEndType().equals(otherEnd.getType().getKey())) { + LOG.debug("Ignoring mismatching anyType {}", relationshipTO.getOtherEndType()); + } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) { + SyncopeClientException noRight = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + noRight.getElements().add( + "Relationships shall be created or updated only from their left end"); + scce.addException(noRight); + } else if (relationships.contains(Pair.of(otherEnd.getKey(), relationshipType.getKey()))) { + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() + + " in relationship " + relationshipTO.getType()); + scce.addException(assigned); + } else { + relationships.add(Pair.of(otherEnd.getKey(), relationshipType.getKey())); + + Relationship relationship = anyUtils.add(any, relationshipType, otherEnd); + + // relationship attributes + relationshipPlainAttrsOnCreate(anyTO, any, relationship, relationshipTO, scce); + } + } + }); + + // 2. attributes SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); anyCR.getPlainAttrs().stream(). @@ -551,7 +696,7 @@ protected void fill( scce.addException(requiredValuesMissing); } - // 2. resources + // 3. resources anyCR.getResources().forEach(resource -> resourceDAO.findById(resource).ifPresentOrElse( any::add, () -> LOG.debug("Invalid {} {}, ignoring...", ExternalResource.class.getSimpleName(), resource))); @@ -560,49 +705,75 @@ protected void fill( if (!requiredValuesMissing.isEmpty()) { scce.addException(requiredValuesMissing); } + } - // 3. relationships - Set> relationships = new HashSet<>(); - anyCR.getRelationships().forEach(relationshipTO -> { - if (StringUtils.isBlank(relationshipTO.getOtherEndType()) - || AnyTypeKind.USER.name().equals(relationshipTO.getOtherEndType()) - || AnyTypeKind.GROUP.name().equals(relationshipTO.getOtherEndType())) { + protected void memberships( + final List memberships, + final AnyTO anyTO, + final Groupable any, + final AnyUtils anyUtils, + final SyncopeClientCompositeException scce) { - SyncopeClientException invalidAnyType = - SyncopeClientException.build(ClientExceptionType.InvalidAnyType); - invalidAnyType.getElements().add(AnyType.class.getSimpleName() - + " not allowed for relationship: " + relationshipTO.getOtherEndType()); - scce.addException(invalidAnyType); + Set groups = new HashSet<>(); + memberships.forEach(membershipTO -> { + Group group = membershipTO.getGroupKey() == null + ? groupDAO.findByName(membershipTO.getGroupName()).orElse(null) + : groupDAO.findById(membershipTO.getGroupKey()).orElse(null); + if (group == null) { + LOG.debug("Ignoring invalid group {}", + membershipTO.getGroupKey() + " / " + membershipTO.getGroupName()); + } else if (groups.contains(group.getKey())) { + LOG.error("{} was already assigned to {}", group, any); + + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidMembership); + assigned.getElements().add("Group " + group.getName() + " was already assigned"); + scce.addException(assigned); } else { - AnyObject otherEnd = anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null); - if (otherEnd == null) { - LOG.debug("Ignoring invalid anyObject {}", relationshipTO.getOtherEndKey()); - } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) { - SyncopeClientException noRight = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - noRight.getElements().add( - "Relationships shall be created or updated only from their left end"); - scce.addException(noRight); - } else if (relationships.contains(Pair.of(otherEnd.getKey(), relationshipTO.getType()))) { - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() - + " in relationship " + relationshipTO.getType()); - scce.addException(assigned); - } else { - relationships.add(Pair.of(otherEnd.getKey(), relationshipTO.getType())); + groups.add(group.getKey()); - relationshipTypeDAO.findById(relationshipTO.getType()).ifPresentOrElse( - rt -> anyUtils.addRelationship((Relatable) any, rt, otherEnd), - () -> LOG.debug("Ignoring invalid relationship type {}", relationshipTO.getType())); - } + Membership newMembership = anyUtils.add(any, group); + + // membership attributes + membershipPlainAttrsOnCreate(anyTO, any, newMembership, membershipTO, scce); } }); } - protected void fill( + protected void relationshipPlainAttrsOnCreate( final AnyTO anyTO, - final Any any, + final Relatable any, + final Relationship relationship, + final RelationshipTO relationshipTO, + final SyncopeClientCompositeException scce) { + + SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); + + relationshipTO.getPlainAttrs().stream(). + filter(attrTO -> !attrTO.getValues().isEmpty()). + forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresent(schema -> { + + PlainAttr attr = any.getPlainAttr(schema.getKey(), relationship).orElseGet(() -> { + PlainAttr gpa = new PlainAttr(); + gpa.setRelationship(relationship.getKey()); + gpa.setPlainSchema(schema); + return gpa; + }); + fillAttr(anyTO, attrTO.getValues(), schema, attr, invalidValues); + + if (!attr.getValuesAsStrings().isEmpty()) { + any.add(attr); + } + })); + + if (!invalidValues.isEmpty()) { + scce.addException(invalidValues); + } + } + + protected void membershipPlainAttrsOnCreate( + final AnyTO anyTO, + final Groupable any, final Membership membership, final MembershipTO membershipTO, final SyncopeClientCompositeException scce) { @@ -613,7 +784,7 @@ protected void fill( filter(attrTO -> !attrTO.getValues().isEmpty()). forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresent(schema -> { - PlainAttr attr = ((Groupable) any).getPlainAttr(schema.getKey(), membership).orElseGet(() -> { + PlainAttr attr = any.getPlainAttr(schema.getKey(), membership).orElseGet(() -> { PlainAttr gpa = new PlainAttr(); gpa.setMembership(membership.getKey()); gpa.setPlainSchema(schema); @@ -630,4 +801,78 @@ protected void fill( scce.addException(invalidValues); } } + + protected void relationshipPlainAttrsOnUpdate( + final Set plainAttrs, + final AnyTO anyTO, + final Relatable any, + final Relationship relationship, + final SyncopeClientCompositeException scce) { + + SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); + + plainAttrs.forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresentOrElse( + schema -> any.getPlainAttr(schema.getKey(), relationship).ifPresentOrElse( + attr -> LOG.debug( + "Plain attribute found for {} and relationship {} with {}, nothing to do", + schema, relationship.getType().getKey(), relationship.getRightEnd()), + () -> { + LOG.debug("No plain attribute found for {} and relationship {} with {}", + schema, relationship.getType().getKey(), relationship.getRightEnd()); + + PlainAttr newAttr = new PlainAttr(); + newAttr.setRelationship(relationship.getKey()); + newAttr.setPlainSchema(schema); + any.add(newAttr); + + processAttrPatch( + anyTO, + any, + new AttrPatch.Builder(attrTO).build(), + schema, + newAttr, + invalidValues); + }), + () -> LOG.debug("Invalid {}{}, ignoring...", PlainSchema.class.getSimpleName(), attrTO.getSchema()))); + if (!invalidValues.isEmpty()) { + scce.addException(invalidValues); + } + } + + protected void membershipPlainAttrsOnUpdate( + final Set plainAttrs, + final AnyTO anyTO, + final Groupable any, + final Membership membership, + final SyncopeClientCompositeException scce) { + + SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); + + plainAttrs.forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresentOrElse( + schema -> any.getPlainAttr(schema.getKey(), membership).ifPresentOrElse( + attr -> LOG.debug( + "Plain attribute found for {} and membership of {}, nothing to do", + schema, membership.getRightEnd()), + () -> { + LOG.debug("No plain attribute found for {} and membership of {}", + schema, membership.getRightEnd()); + + PlainAttr newAttr = new PlainAttr(); + newAttr.setMembership(membership.getKey()); + newAttr.setPlainSchema(schema); + any.add(newAttr); + + processAttrPatch( + anyTO, + any, + new AttrPatch.Builder(attrTO).build(), + schema, + newAttr, + invalidValues); + }), + () -> LOG.debug("Invalid {}{}, ignoring...", PlainSchema.class.getSimpleName(), attrTO.getSchema()))); + if (!invalidValues.isEmpty()) { + scce.addException(invalidValues); + } + } } 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 ae09217b269..70bb2657204 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 @@ -18,7 +18,6 @@ */ package org.apache.syncope.core.provisioning.java.data; -import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -28,15 +27,12 @@ import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.request.AnyObjectCR; import org.apache.syncope.common.lib.request.AnyObjectUR; -import org.apache.syncope.common.lib.request.AttrPatch; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.ConnObject; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.ClientExceptionType; -import org.apache.syncope.common.lib.types.PatchOperation; -import org.apache.syncope.common.lib.types.ResourceOperation; import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager; import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO; @@ -51,12 +47,9 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtils; import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; import org.apache.syncope.core.persistence.api.entity.EntityFactory; -import org.apache.syncope.core.persistence.api.entity.PlainAttr; -import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.Realm; -import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; -import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.provisioning.api.DerAttrHandler; import org.apache.syncope.core.provisioning.api.IntAttrNameParser; import org.apache.syncope.core.provisioning.api.MappingManager; @@ -144,21 +137,22 @@ public AnyObjectTO getAnyObjectTO(final AnyObject anyObject, final boolean detai // relationships anyObjectTO.getRelationships().addAll( anyObjectDAO.findAllRelationships(anyObject).stream(). - map(r -> getRelationshipTO( - r.getType().getKey(), - r.getLeftEnd().getKey().equals(anyObject.getKey()) + map(relationship -> getRelationshipTO(anyObject.getPlainAttrs(relationship), + derAttrHandler.getValues(anyObject, relationship), + relationship.getType().getKey(), + relationship.getLeftEnd().getKey().equals(anyObject.getKey()) ? RelationshipTO.End.LEFT : RelationshipTO.End.RIGHT, - r.getLeftEnd().getKey().equals(anyObject.getKey()) - ? r.getRightEnd() - : r.getLeftEnd())). + relationship.getLeftEnd().getKey().equals(anyObject.getKey()) + ? relationship.getRightEnd() + : relationship.getLeftEnd())). toList()); // memberships anyObjectTO.getMemberships().addAll( anyObject.getMemberships().stream().map(membership -> getMembershipTO( anyObject.getPlainAttrs(membership), - derAttrHandler.getValues(anyObject, membership), + derAttrHandler.getValues((Groupable) anyObject, membership), membership)).toList()); // dynamic memberships @@ -184,6 +178,8 @@ public void create(final AnyObject anyObject, final AnyObjectCR anyObjectCR) { AnyObjectTO anyTO = new AnyObjectTO(); EntityTOUtils.toAnyTO(anyObjectCR, anyTO); + AnyUtils anyUtils = anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT); + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); // name @@ -209,33 +205,7 @@ public void create(final AnyObject anyObject, final AnyObjectCR anyObjectCR) { fill(anyTO, anyObject, anyObjectCR, anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce); // memberships - Set groups = new HashSet<>(); - anyObjectCR.getMemberships().forEach(membershipTO -> { - Group group = membershipTO.getGroupKey() == null - ? groupDAO.findByName(membershipTO.getGroupName()).orElse(null) - : groupDAO.findById(membershipTO.getGroupKey()).orElse(null); - if (group == null) { - LOG.debug("Ignoring invalid group {} / {}", membershipTO.getGroupKey(), membershipTO.getGroupName()); - } else if (groups.contains(group.getKey())) { - LOG.error("{} was already assigned to {}", group, anyObject); - - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidMembership); - assigned.getElements().add("Group " + group.getName() + " was already assigned"); - scce.addException(assigned); - } else { - groups.add(group.getKey()); - - AMembership membership = entityFactory.newEntity(AMembership.class); - membership.setRightEnd(group); - membership.setLeftEnd(anyObject); - - anyObject.add(membership); - - // membership attributes - fill(anyTO, anyObject, membership, membershipTO, scce); - } - }); + memberships(anyObjectCR.getMemberships(), anyTO, anyObject, anyUtils, scce); // Throw composite exception if there is at least one element set in the composing exceptions if (scce.hasExceptions()) { @@ -269,77 +239,18 @@ public PropagationByResource update(final AnyObject toBeUpdated, final A } // attributes, resources and relationships - fill(anyTO, anyObject, anyObjectUR, anyUtils, scce); - - SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); + fill(anyTO, anyObject, anyObjectUR, propByRes, anyUtils, scce); // memberships - Set groups = new HashSet<>(); - anyObjectUR.getMemberships().stream().filter(patch -> patch.getGroup() != null).forEach(patch -> { - anyObject.getMembership(patch.getGroup()).ifPresent(membership -> { - anyObject.remove(membership); - membership.setLeftEnd(null); - anyObject.getPlainAttrs(membership).forEach(anyObject::remove); - anyObjectDAO.deleteMembership(membership); - - if (patch.getOperation() == PatchOperation.DELETE) { - propByRes.addAll( - ResourceOperation.UPDATE, - groupDAO.findAllResourceKeys((membership.getRightEnd().getKey()))); - } - }); - if (patch.getOperation() == PatchOperation.ADD_REPLACE) { - Group group = groupDAO.findById(patch.getGroup()).orElse(null); - if (group == null) { - LOG.debug("Ignoring invalid group {}", patch.getGroup()); - } else if (groups.contains(group.getKey())) { - LOG.error("Multiple patches for group {} of {} were found", group, anyObject); - - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidMembership); - assigned.getElements().add("Multiple patches for group " + group.getName() + " were found"); - scce.addException(assigned); - } else { - groups.add(group.getKey()); - - AMembership newMembership = entityFactory.newEntity(AMembership.class); - newMembership.setRightEnd(group); - newMembership.setLeftEnd(anyObject); - - anyObject.add(newMembership); - - patch.getPlainAttrs().forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresentOrElse( - schema -> anyObject.getPlainAttr(schema.getKey(), newMembership).ifPresentOrElse( - attr -> LOG.debug( - "Plain attribute found for {} and membership of {}, nothing to do", - schema, newMembership.getRightEnd()), - () -> { - LOG.debug("No plain attribute found for {} and membership of {}", - schema, newMembership.getRightEnd()); - - PlainAttr newAttr = new PlainAttr(); - newAttr.setMembership(newMembership.getKey()); - newAttr.setPlainSchema(schema); - anyObject.add(newAttr); - - processAttrPatch( - anyTO, - anyObject, - new AttrPatch.Builder(attrTO).build(), - schema, - newAttr, - invalidValues); - }), - () -> LOG.debug("Invalid {}{}, ignoring...", - PlainSchema.class.getSimpleName(), attrTO.getSchema()))); - if (!invalidValues.isEmpty()) { - scce.addException(invalidValues); - } - - propByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey())); - } - } - }); + memberships( + anyObjectUR.getMemberships(), + anyTO, + anyObject, + group -> { + }, + propByRes, + anyUtils, + scce); // Throw composite exception if there is at least one element set in the composing exceptions if (scce.hasExceptions()) { 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 b459ea7ef3d..a955fc678fd 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 @@ -55,9 +55,10 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.Realm; +import org.apache.syncope.core.persistence.api.entity.Relatable; import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.api.search.SearchCondConverter; @@ -201,7 +202,7 @@ public void create(final Group group, final GroupCR groupCR) { // type extensions groupCR.getTypeExtensions().forEach(typeExtTO -> anyTypeDAO.findById(typeExtTO.getAnyType()).ifPresentOrElse( anyType -> { - TypeExtension typeExt = entityFactory.newEntity(TypeExtension.class); + GroupTypeExtension typeExt = entityFactory.newEntity(GroupTypeExtension.class); typeExt.setAnyType(anyType); typeExt.setGroup(group); group.add(typeExt); @@ -230,6 +231,8 @@ public PropagationByResource update(final Group toBeUpdated, final Group GroupTO anyTO = AnyOperations.patch(getGroupTO(group, true), groupUR); + PropagationByResource propByRes = new PropagationByResource<>(); + // Save projection on Resources (before update) Map beforeOnResources = onResources(group, groupDAO.findAllResourceKeys(group.getKey()), null, Set.of()); @@ -284,7 +287,7 @@ public PropagationByResource update(final Group toBeUpdated, final Group } // attributes, resources and relationships - fill(anyTO, group, groupUR, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce); + fill(anyTO, group, groupUR, propByRes, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce); group = groupDAO.save(group); @@ -321,9 +324,9 @@ public PropagationByResource update(final Group toBeUpdated, final Group if (anyType == null) { LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), typeExtTO.getAnyType()); } else { - TypeExtension typeExt = group.getTypeExtension(anyType).orElse(null); + GroupTypeExtension typeExt = group.getTypeExtension(anyType).orElse(null); if (typeExt == null) { - typeExt = entityFactory.newEntity(TypeExtension.class); + typeExt = entityFactory.newEntity(GroupTypeExtension.class); typeExt.setAnyType(anyType); typeExt.setGroup(group); group.add(typeExt); @@ -362,18 +365,17 @@ public PropagationByResource update(final Group toBeUpdated, final Group group = groupDAO.save(group); // Build final information for next stage (propagation) - PropagationByResource propByRes = propByRes( - beforeOnResources, onResources(group, groupDAO.findAllResourceKeys(group.getKey()), null, Set.of())); + propByRes.merge(propByRes( + beforeOnResources, onResources(group, groupDAO.findAllResourceKeys(group.getKey()), null, Set.of()))); propByRes.merge(ownerPropByRes); return propByRes; } @Override - public TypeExtensionTO getTypeExtensionTO(final TypeExtension typeExt) { + public TypeExtensionTO getTypeExtensionTO(final GroupTypeExtension typeExt) { TypeExtensionTO typeExtTO = new TypeExtensionTO(); typeExtTO.setAnyType(typeExt.getAnyType().getKey()); - typeExtTO.getAuxClasses().addAll( - typeExt.getAuxClasses().stream().map(AnyTypeClass::getKey).toList()); + typeExtTO.getAuxClasses().addAll(typeExt.getAuxClasses().stream().map(AnyTypeClass::getKey).toList()); return typeExtTO; } @@ -424,8 +426,12 @@ public GroupTO getGroupTO(final Group group, final boolean details) { if (details) { // relationships - groupTO.getRelationships().addAll(group.getRelationships().stream().map(r -> getRelationshipTO( - r.getType().getKey(), RelationshipTO.End.LEFT, r.getRightEnd())).toList()); + groupTO.getRelationships().addAll(group.getRelationships().stream(). + map(relationship -> getRelationshipTO(group.getPlainAttrs(relationship), + derAttrHandler.getValues((Relatable) group, relationship), + relationship.getType().getKey(), + RelationshipTO.End.LEFT, + relationship.getRightEnd())).toList()); } return groupTO; diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java index 3d11de7ce17..4cd1f4db17b 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java @@ -20,20 +20,36 @@ import java.util.Optional; import org.apache.syncope.common.lib.to.RelationshipTypeTO; +import org.apache.syncope.common.lib.to.TypeExtensionTO; +import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.provisioning.api.data.RelationshipTypeDataBinder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RelationshipTypeDataBinderImpl implements RelationshipTypeDataBinder { + protected static final Logger LOG = LoggerFactory.getLogger(RelationshipTypeDataBinder.class); + protected final AnyTypeDAO anyTypeDAO; + protected final AnyTypeClassDAO anyTypeClassDAO; + protected final EntityFactory entityFactory; - public RelationshipTypeDataBinderImpl(final AnyTypeDAO anyTypeDAO, final EntityFactory entityFactory) { + public RelationshipTypeDataBinderImpl( + final AnyTypeDAO anyTypeDAO, + final AnyTypeClassDAO anyTypeClassDAO, + final EntityFactory entityFactory) { + this.anyTypeDAO = anyTypeDAO; + this.anyTypeClassDAO = anyTypeClassDAO; this.entityFactory = entityFactory; } @@ -60,6 +76,50 @@ public void update(final RelationshipType relationshipType, final RelationshipTy } relationshipType.setDescription(relationshipTypeTO.getDescription()); + + // type extensions + relationshipTypeTO.getTypeExtensions(). + forEach(typeExtTO -> anyTypeDAO.findById(typeExtTO.getAnyType()).ifPresentOrElse(anyType -> { + + RelationshipTypeExtension typeExt = relationshipType.getTypeExtension(anyType).orElse(null); + if (typeExt == null) { + typeExt = entityFactory.newEntity(RelationshipTypeExtension.class); + typeExt.setAnyType(anyType); + typeExt.setRelationshipType(relationshipType); + relationshipType.add(typeExt); + } + + // add all classes contained in the TO + for (String key : typeExtTO.getAuxClasses()) { + AnyTypeClass anyTypeClass = anyTypeClassDAO.findById(key).orElse(null); + if (anyTypeClass == null) { + LOG.warn("Ignoring invalid {}: {}", AnyTypeClass.class.getSimpleName(), key); + } else { + typeExt.add(anyTypeClass); + } + } + // remove all classes not contained in the TO + typeExt.getAuxClasses(). + removeIf(anyTypeClass -> !typeExtTO.getAuxClasses().contains(anyTypeClass.getKey())); + + // only consider non-empty type extensions + if (typeExt.getAuxClasses().isEmpty()) { + relationshipType.getTypeExtensions().remove(typeExt); + typeExt.setRelationshipType(null); + } + + }, () -> LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), typeExtTO.getAnyType()))); + + // remove all type extensions not contained in the TO + relationshipType.getTypeExtensions(). + removeIf(typeExt -> relationshipTypeTO.getTypeExtension(typeExt.getAnyType().getKey()).isEmpty()); + } + + protected TypeExtensionTO getTypeExtensionTO(final RelationshipTypeExtension typeExt) { + TypeExtensionTO typeExtTO = new TypeExtensionTO(); + typeExtTO.setAnyType(typeExt.getAnyType().getKey()); + typeExtTO.getAuxClasses().addAll(typeExt.getAuxClasses().stream().map(AnyTypeClass::getKey).toList()); + return typeExtTO; } @Override @@ -71,6 +131,9 @@ public RelationshipTypeTO getRelationshipTypeTO(final RelationshipType relations relationshipTypeTO.setLeftEndAnyType(relationshipType.getLeftEndAnyType().getKey()); relationshipTypeTO.setRightEndAnyType(relationshipType.getRightEndAnyType().getKey()); + relationshipType.getTypeExtensions(). + forEach(typeExt -> relationshipTypeTO.getTypeExtensions().add(getTypeExtensionTO(typeExt))); + return relationshipTypeTO; } } 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 910335fb93f..6c127681342 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 @@ -33,7 +33,6 @@ import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.SyncopeClientCompositeException; import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.request.AttrPatch; import org.apache.syncope.common.lib.request.PasswordPatch; import org.apache.syncope.common.lib.request.StringPatchItem; import org.apache.syncope.common.lib.request.UserCR; @@ -69,14 +68,12 @@ import org.apache.syncope.core.persistence.api.entity.Delegation; import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.Groupable; import org.apache.syncope.core.persistence.api.entity.PlainAttr; -import org.apache.syncope.core.persistence.api.entity.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.Role; -import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion; -import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.provisioning.api.DerAttrHandler; import org.apache.syncope.core.provisioning.api.IntAttrNameParser; @@ -280,6 +277,8 @@ public void create(final User user, final UserCR userCR) { UserTO anyTO = new UserTO(); EntityTOUtils.toAnyTO(userCR, anyTO); + AnyUtils anyUtils = anyUtilsFactory.getInstance(AnyTypeKind.USER); + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); // set username @@ -319,34 +318,7 @@ public void create(final User user, final UserCR userCR) { fill(anyTO, user, userCR, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce); // memberships - Set groups = new HashSet<>(); - userCR.getMemberships().forEach(membershipTO -> { - Group group = membershipTO.getGroupKey() == null - ? groupDAO.findByName(membershipTO.getGroupName()).orElse(null) - : groupDAO.findById(membershipTO.getGroupKey()).orElse(null); - if (group == null) { - LOG.debug("Ignoring invalid group {}", - membershipTO.getGroupKey() + " / " + membershipTO.getGroupName()); - } else if (groups.contains(group.getKey())) { - LOG.error("{} was already assigned to {}", group, user); - - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidMembership); - assigned.getElements().add("Group " + group.getName() + " was already assigned"); - scce.addException(assigned); - } else { - groups.add(group.getKey()); - - UMembership membership = entityFactory.newEntity(UMembership.class); - membership.setRightEnd(group); - membership.setLeftEnd(user); - - user.add(membership); - - // membership attributes - fill(anyTO, user, membership, membershipTO, scce); - } - }); + memberships(userCR.getMemberships(), anyTO, user, anyUtils, scce); // linked accounts SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); @@ -459,75 +431,16 @@ public UserWorkflowResult.PropagationInfo update(final User toBeUpdated, final U } // attributes, resources and relationships - fill(anyTO, user, userUR, anyUtils, scce); + fill(anyTO, user, userUR, propByRes, anyUtils, scce); SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues); // memberships - Set groups = new HashSet<>(); - userUR.getMemberships().stream().filter(patch -> patch.getGroup() != null).forEach(patch -> { - user.getMembership(patch.getGroup()).ifPresent(membership -> { - user.remove(membership); - membership.setLeftEnd(null); - user.getPlainAttrs(membership).forEach(user::remove); - userDAO.deleteMembership(membership); - - if (patch.getOperation() == PatchOperation.DELETE) { - propByRes.addAll( - ResourceOperation.UPDATE, - groupDAO.findAllResourceKeys((membership.getRightEnd().getKey()))); - } - }); - if (patch.getOperation() == PatchOperation.ADD_REPLACE) { - Group group = groupDAO.findById(patch.getGroup()).orElse(null); - if (group == null) { - LOG.debug("Ignoring invalid group {}", patch.getGroup()); - } else if (groups.contains(group.getKey())) { - LOG.error("Multiple patches for group {} of {} were found", group, user); - - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidMembership); - assigned.getElements().add("Multiple patches for group " + group.getName() + " were found"); - scce.addException(assigned); - } else { - groups.add(group.getKey()); - - UMembership newMembership = entityFactory.newEntity(UMembership.class); - newMembership.setRightEnd(group); - newMembership.setLeftEnd(user); - - user.add(newMembership); - - patch.getPlainAttrs().forEach(attrTO -> getPlainSchema(attrTO.getSchema()).ifPresentOrElse( - schema -> user.getPlainAttr(schema.getKey(), newMembership).ifPresentOrElse( - attr -> LOG.debug( - "Plain attribute found for {} and membership of {}, nothing to do", - schema, newMembership.getRightEnd()), - () -> { - LOG.debug("No plain attribute found for {} and membership of {}", - schema, newMembership.getRightEnd()); - - PlainAttr newAttr = new PlainAttr(); - newAttr.setMembership(newMembership.getKey()); - newAttr.setPlainSchema(schema); - user.add(newAttr); - - processAttrPatch( - anyTO, - user, - new AttrPatch.Builder(attrTO).build(), - schema, - newAttr, - invalidValues); - }), - () -> LOG.debug("Invalid {}{}, ignoring...", - PlainSchema.class.getSimpleName(), attrTO.getSchema()))); - if (!invalidValues.isEmpty()) { - scce.addException(invalidValues); - } - - propByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey())); - + memberships( + userUR.getMemberships(), + anyTO, + user, + group -> { // SYNCOPE-686: if password is invertible and we are adding resources with password mapping, // ensure that they are counted for password propagation if (toBeUpdated.canDecodeSecrets()) { @@ -538,9 +451,10 @@ public UserWorkflowResult.PropagationInfo update(final User toBeUpdated, final U filter(this::isPasswordMapped). forEach(resource -> userUR.getPassword().getResources().add(resource.getKey())); } - } - } - }); + }, + propByRes, + anyUtils, + scce); // linked accounts userUR.getLinkedAccounts().stream().filter(patch -> patch.getLinkedAccountTO() != null).forEach(patch -> { @@ -675,13 +589,17 @@ public UserTO getUserTO(final User user, final boolean details) { userDAO.findDynRoles(user.getKey()).stream().map(Role::getKey).toList()); // relationships - userTO.getRelationships().addAll(user.getRelationships().stream().map(r -> getRelationshipTO( - r.getType().getKey(), RelationshipTO.End.LEFT, r.getRightEnd())).toList()); + userTO.getRelationships().addAll(user.getRelationships().stream(). + map(relationship -> getRelationshipTO(user.getPlainAttrs(relationship), + derAttrHandler.getValues(user, relationship), + relationship.getType().getKey(), + RelationshipTO.End.LEFT, + relationship.getRightEnd())).toList()); // memberships userTO.getMemberships().addAll(user.getMemberships().stream(). map(membership -> getMembershipTO(user.getPlainAttrs(membership), - derAttrHandler.getValues(user, membership), + derAttrHandler.getValues((Groupable) user, membership), membership)).toList()); // dynamic memberships diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java index 3fb10bea321..5838c75cae6 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import org.apache.commons.jexl3.JexlContext; import org.apache.commons.jexl3.MapContext; import org.apache.commons.lang3.StringUtils; @@ -46,6 +47,7 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.NotificationDAO; +import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.TaskDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.Any; @@ -58,6 +60,7 @@ import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; import org.apache.syncope.core.persistence.api.entity.task.TaskExec; import org.apache.syncope.core.persistence.api.entity.user.UMembership; +import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.persistence.api.search.SearchCondConverter; import org.apache.syncope.core.persistence.api.search.SearchCondVisitor; @@ -76,6 +79,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; @Transactional(rollbackFor = { Throwable.class }) public class DefaultNotificationManager implements NotificationManager { @@ -98,6 +102,8 @@ public class DefaultNotificationManager implements NotificationManager { protected final TaskDAO taskDAO; + protected final RelationshipTypeDAO relationshipTypeDAO; + protected final DerAttrHandler derAttrHandler; protected final UserDataBinder userDataBinder; @@ -127,6 +133,7 @@ public DefaultNotificationManager( final AnySearchDAO anySearchDAO, final AnyMatchDAO anyMatchDAO, final TaskDAO taskDAO, + final RelationshipTypeDAO relationshipTypeDAO, final DerAttrHandler derAttrHandler, final UserDataBinder userDataBinder, final GroupDataBinder groupDataBinder, @@ -145,6 +152,7 @@ public DefaultNotificationManager( this.anySearchDAO = anySearchDAO; this.anyMatchDAO = anyMatchDAO; this.taskDAO = taskDAO; + this.relationshipTypeDAO = relationshipTypeDAO; this.derAttrHandler = derAttrHandler; this.userDataBinder = userDataBinder; this.groupDataBinder = groupDataBinder; @@ -379,12 +387,20 @@ protected String getRecipientEmail(final String recipientAttrName, final User us flatMap(groupDAO::findByName). flatMap(group -> user.getMembership(group.getKey())). orElse(null); + URelationship relationship = Optional.ofNullable(intAttrName.getRelationshipType()). + flatMap(relationshipTypeDAO::findById). + map(user::getRelationships). + filter(Predicate.not(CollectionUtils::isEmpty)). + map(List::getFirst). + orElse(null); switch (intAttrName.getSchemaType()) { case PLAIN -> { - Optional attr = membership == null + Optional attr = membership == null && relationship == null ? user.getPlainAttr(recipientAttrName) - : user.getPlainAttr(recipientAttrName, membership); + : relationship == null + ? user.getPlainAttr(recipientAttrName, membership) + : user.getPlainAttr(recipientAttrName, relationship); email = attr.map(a -> a.getValuesAsStrings().isEmpty() ? null : a.getValuesAsStrings().getFirst()). @@ -393,9 +409,11 @@ protected String getRecipientEmail(final String recipientAttrName, final User us case DERIVED -> { email = derSchemaDAO.findById(recipientAttrName). - map(derSchema -> membership == null + map(derSchema -> membership == null && relationship == null ? derAttrHandler.getValue(user, derSchema) - : derAttrHandler.getValue(user, membership, derSchema)). + : relationship == null + ? derAttrHandler.getValue(user, membership, derSchema) + : derAttrHandler.getValue(user, relationship, derSchema)). orElse(null); } diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java index d89b80b761b..64587ffded6 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java @@ -76,7 +76,7 @@ public static void unsetAuthContext() { private ExternalResourceDAO resourceDAO; @Autowired - private ResourceDataBinder resourceDataBinder; + private ResourceDataBinder binder; @Autowired private PlainSchemaDAO plainSchemaDAO; @@ -119,7 +119,7 @@ public void issue42() { item.setPurpose(MappingPurpose.BOTH); mapping.setConnObjectKeyItem(item); - ExternalResource resource = resourceDataBinder.create(resourceTO); + ExternalResource resource = binder.create(resourceTO); resource = resourceDAO.save(resource); entityManager.flush(); assertNotNull(resource); diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java index e24d04870e0..f633fa2a41e 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java @@ -25,16 +25,27 @@ import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.request.AttrPatch; import org.apache.syncope.common.lib.request.MembershipUR; +import org.apache.syncope.common.lib.request.RelationshipUR; import org.apache.syncope.common.lib.request.UserUR; +import org.apache.syncope.common.lib.to.RelationshipTypeTO; +import org.apache.syncope.common.lib.to.TypeExtensionTO; +import org.apache.syncope.common.lib.types.AnyEntitlement; +import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.IdRepoEntitlement; +import org.apache.syncope.core.persistence.api.EncryptorManager; import org.apache.syncope.core.persistence.api.attrvalue.InvalidEntityException; +import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.user.UMembership; +import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.data.RelationshipTypeDataBinder; import org.apache.syncope.core.provisioning.api.data.UserDataBinder; import org.apache.syncope.core.provisioning.java.AbstractTest; import org.apache.syncope.core.spring.security.SyncopeAuthenticationDetails; @@ -53,7 +64,9 @@ public class UserDataBinderTest extends AbstractTest { @BeforeAll public static void setAuthContext() { - List authorities = IdRepoEntitlement.values().stream(). + List authorities = Stream.concat( + IdRepoEntitlement.values().stream(), + Stream.of(AnyEntitlement.READ.getFor("PRINTER"))). map(entitlement -> new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM)). collect(Collectors.toList()); @@ -70,11 +83,20 @@ public static void unsetAuthContext() { } @Autowired - private UserDataBinder dataBinder; + private UserDataBinder binder; + + @Autowired + private RelationshipTypeDataBinder relationshipTypeDataBinder; @Autowired private UserDAO userDAO; + @Autowired + private RelationshipTypeDAO relationshipTypeDAO; + + @Autowired + private EncryptorManager encryptorManager; + @Test public void membershipWithAttrNotAllowed() { UserUR userUR = new UserUR.Builder("1417acbe-cbf6-4277-9372-e75e04f97000").build(); @@ -90,7 +112,7 @@ public void membershipWithAttrNotAllowed() { assertThrows( InvalidEntityException.class, - () -> dataBinder.update(userDAO.findById(userUR.getKey()).orElseThrow(), userUR)); + () -> binder.update(userDAO.findById(userUR.getKey()).orElseThrow(), userUR)); } @Test @@ -105,16 +127,52 @@ public void membershipWithAttr() { userUR.getMemberships().add(new MembershipUR.Builder("034740a9-fa10-453b-af37-dc7897e98fb1"). plainAttr(new Attr.Builder("obscure").value("testvalue2").build()).build()); - dataBinder.update(userDAO.findById(userUR.getKey()).orElseThrow(), userUR); + binder.update(userDAO.findById(userUR.getKey()).orElseThrow(), userUR); User user = userDAO.findById(userUR.getKey()).orElseThrow(); UMembership newM = user.getMembership("034740a9-fa10-453b-af37-dc7897e98fb1").orElseThrow(); assertEquals(1, user.getPlainAttrs(newM).size()); assertNull(user.getPlainAttr("obscure").orElseThrow().getMembership()); - assertEquals(2, user.getPlainAttrs("obscure").size()); - assertTrue(user.getPlainAttrs("obscure").contains(user.getPlainAttr("obscure").orElseThrow())); - assertTrue(user.getPlainAttrs("obscure").stream().anyMatch(a -> a.getMembership() == null)); - assertTrue(user.getPlainAttrs("obscure").stream().anyMatch(a -> newM.getKey().equals(a.getMembership()))); + assertTrue(encryptorManager.getInstance().verify( + "testvalue2", + user.getCipherAlgorithm(), + user.getPlainAttr("obscure", newM).orElseThrow().getValuesAsStrings().getFirst())); + } + + @Test + public void relationshipWithAttr() { + // first add type extension to neighborhood + RelationshipType relType = relationshipTypeDAO.findById("neighborhood").orElseThrow(); + RelationshipTypeTO relTypeTO = relationshipTypeDataBinder.getRelationshipTypeTO(relType); + + TypeExtensionTO typeExt = new TypeExtensionTO(); + typeExt.setAnyType(AnyTypeKind.USER.name()); + typeExt.getAuxClasses().add("other"); + relTypeTO.getTypeExtensions().add(typeExt); + + relationshipTypeDataBinder.update(relType, relTypeTO); + relType = relationshipTypeDAO.save(relType); + relTypeTO = relationshipTypeDataBinder.getRelationshipTypeTO(relType); + assertEquals("other", relTypeTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().getFirst()); + + // then add relationship with attribute + UserUR userUR = new UserUR.Builder("1417acbe-cbf6-4277-9372-e75e04f97000"). + relationship(new RelationshipUR.Builder("neighborhood"). + otherEnd("PRINTER", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). + plainAttr(new Attr.Builder("obscure").value("testvalue3").build()). + build()). + build(); + + binder.update(userDAO.findById(userUR.getKey()).orElseThrow(), userUR); + + User user = userDAO.findById(userUR.getKey()).orElseThrow(); + URelationship newR = user.getRelationship(relType, "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").orElseThrow(); + assertEquals(1, user.getPlainAttrs(newR).size()); + + assertTrue(encryptorManager.getInstance().verify( + "testvalue3", + user.getCipherAlgorithm(), + user.getPlainAttr("obscure", newR).orElseThrow().getValuesAsStrings().getFirst())); } } diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java index d1077028dc7..1e6a1778041 100644 --- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java +++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java @@ -46,12 +46,16 @@ protected void doExecute(final DelegateExecution execution) { user = userDAO.save(user); UserUR req = AnyOperations.diff(userTO, dataBinder.getUserTO(user, true), false); - // don't mess with password, as the cleartext values was already properly saved - req.setPassword(null); + if (req.isEmpty()) { + LOG.debug("Nothing to change, skip user update"); + } else { + // don't mess with password, as the cleartext values was already properly saved + req.setPassword(null); - dataBinder.update(user, req); + dataBinder.update(user, req); - execution.setVariable(FlowableRuntimeUtils.USER, user); + execution.setVariable(FlowableRuntimeUtils.USER, user); + } } execution.setVariable(FlowableRuntimeUtils.PROPAGATE_ENABLE, Boolean.TRUE); diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java index 4f1a64b751b..cfd9c9fc173 100644 --- a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java +++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java @@ -20,7 +20,6 @@ import org.apache.syncope.common.lib.request.RelationshipUR; import org.apache.syncope.common.lib.request.UserUR; -import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils; import org.apache.syncope.core.flowable.task.FlowableServiceTask; import org.apache.syncope.core.persistence.api.dao.UserDAO; @@ -52,8 +51,8 @@ protected void doExecute(final DelegateExecution execution) { UserUR userUR = new UserUR(); userUR.setKey(user.getKey()); - userUR.getRelationships().add(new RelationshipUR.Builder(new RelationshipTO.Builder("neighborhood"). - otherEnd("PRINTER", printer).build()).build()); + userUR.getRelationships().add(new RelationshipUR.Builder("neighborhood"). + otherEnd("PRINTER", printer).build()); UserWorkflowResult.PropagationInfo propInfo = dataBinder.update(user, userUR); 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 4b03db189cc..c4d2fe5ee4e 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 @@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -34,7 +33,6 @@ import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.request.AnyObjectCR; import org.apache.syncope.common.lib.request.AnyObjectUR; -import org.apache.syncope.common.lib.request.RelationshipUR; import org.apache.syncope.common.lib.request.StringPatchItem; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.ConnObject; @@ -185,31 +183,6 @@ public void deleteAttr() { } } - @Test - public void unlimitedRelationships() { - AnyObjectCR anyObjectCR = getSample("unlimited1"); - anyObjectCR.setRealm("/even/two"); - anyObjectCR.getResources().clear(); - AnyObjectTO left = createAnyObject(anyObjectCR).getEntity(); - - anyObjectCR = getSample("unlimited2"); - anyObjectCR.setRealm(SyncopeConstants.ROOT_REALM); - anyObjectCR.getResources().clear(); - anyObjectCR.getRelationships().add(new RelationshipTO.Builder("inclusion"). - otherEnd(left.getType(), left.getKey()).build()); - AnyObjectTO right = createAnyObject(anyObjectCR).getEntity(); - - assertEquals(1, right.getRelationships().size()); - assertEquals(left.getKey(), right.getRelationships().getFirst().getOtherEndKey()); - - AnyObjectUR anyObjectUR = new AnyObjectUR.Builder(left.getKey()). - relationship(new RelationshipUR.Builder(new RelationshipTO.Builder("inclusion"). - otherEnd(right.getType(), right.getKey()).build()).build()).build(); - left = updateAnyObject(anyObjectUR).getEntity(); - assertEquals(2, left.getRelationships().size()); - assertTrue(left.getRelationships().stream().anyMatch(r -> right.getKey().equals(r.getOtherEndKey()))); - } - @Test public void issueSYNCOPE756() { AnyObjectCR anyObjectCR = getSample("issueSYNCOPE756"); @@ -254,77 +227,4 @@ public void issueSYNCOPE1472() { assertFalse(printer.getResources().contains(RESOURCE_NAME_DBSCRIPTED), "Should not contain removed resources"); assertFalse(printer.getAuxClasses().contains("csv"), "Should not contain removed auxiliary classes"); } - - @Test - public void issueSYNCOPE1686() { - // Create printers - AnyObjectCR printer1CR = getSample("printer1"); - printer1CR.getResources().clear(); - String key1 = createAnyObject(printer1CR).getEntity().getKey(); - - AnyObjectCR printer2CR = getSample("printer2"); - printer2CR.getResources().clear(); - String key2 = createAnyObject(printer2CR).getEntity().getKey(); - - AnyObjectCR printer3CR = getSample("printer3"); - printer3CR.getResources().clear(); - String key3 = createAnyObject(printer3CR).getEntity().getKey(); - - // Add relationships: printer1 -> printer2 and printer2 -> printer3 - AnyObjectUR relationship1To2 = new AnyObjectUR.Builder(key1) - .relationship(new RelationshipUR.Builder( - new RelationshipTO.Builder("inclusion").otherEnd(PRINTER, key2).build()).build()) - .build(); - AnyObjectUR relationship2To3 = new AnyObjectUR.Builder(key2) - .relationship(new RelationshipUR.Builder( - new RelationshipTO.Builder("inclusion").otherEnd(PRINTER, key3).build()).build()) - .build(); - - updateAnyObject(relationship1To2); - updateAnyObject(relationship2To3); - - // Read updated printers - AnyObjectTO printer1 = ANY_OBJECT_SERVICE.read(key1); - AnyObjectTO printer2 = ANY_OBJECT_SERVICE.read(key2); - AnyObjectTO printer3 = ANY_OBJECT_SERVICE.read(key3); - - // Verify relationships for printer1 - assertEquals(1, printer1.getRelationships().size()); - RelationshipTO rel1 = printer1.getRelationships().getFirst(); - assertEquals(RelationshipTO.End.LEFT, rel1.getEnd()); - assertEquals(printer2.getKey(), rel1.getOtherEndKey()); - assertEquals(printer2.getType(), rel1.getOtherEndType()); - assertEquals(printer2.getName(), rel1.getOtherEndName()); - - // Verify relationships for printer2 - assertEquals(2, printer2.getRelationships().size()); - assertTrue(printer2.getRelationships().stream() - .anyMatch(r -> r.getEnd() == RelationshipTO.End.LEFT - && printer3.getKey().equals(r.getOtherEndKey()) - && printer3.getType().equals(r.getOtherEndType()) - && printer3.getName().equals(r.getOtherEndName()))); - assertTrue(printer2.getRelationships().stream() - .anyMatch(r -> r.getEnd() == RelationshipTO.End.RIGHT - && printer1.getKey().equals(r.getOtherEndKey()) - && printer1.getType().equals(r.getOtherEndType()) - && printer1.getName().equals(r.getOtherEndName()))); - - // Verify relationships for printer3 - assertEquals(1, printer3.getRelationships().size()); - RelationshipTO rel3 = printer3.getRelationships().getFirst(); - assertEquals(RelationshipTO.End.RIGHT, rel3.getEnd()); - assertEquals(printer2.getKey(), rel3.getOtherEndKey()); - assertEquals(printer2.getType(), rel3.getOtherEndType()); - assertEquals(printer2.getName(), rel3.getOtherEndName()); - - // Test invalid relationship with End.RIGHT - AnyObjectCR printer4CR = getSample("printer4"); - printer4CR.getResources().clear(); - printer4CR.getRelationships().add( - new RelationshipTO.Builder("inclusion", RelationshipTO.End.RIGHT).otherEnd(PRINTER, key1).build()); - - SyncopeClientException e = assertThrows(SyncopeClientException.class, () -> createAnyObject(printer4CR)); - assertEquals(ClientExceptionType.InvalidRelationship, e.getType()); - assertTrue(e.getMessage().contains("Relationships shall be created or updated only from their left end")); - } } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java index 02d8256d994..97873643153 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java @@ -116,10 +116,10 @@ public void misc() throws JsonProcessingException { userUR.getPlainAttrs(). add(new AttrPatch.Builder(new Attr.Builder("aLong").value("1977").build()).build()); - MembershipUR membershipPatch = new MembershipUR.Builder(membership.getGroupKey()).build(); - membershipPatch.getPlainAttrs().add(new Attr.Builder("aLong").value("1976").build()); - membershipPatch.getPlainAttrs().add(new Attr.Builder("ctype").value("membership type").build()); - userUR.getMemberships().add(membershipPatch); + userUR.getMemberships().add(new MembershipUR.Builder(membership.getGroupKey()). + plainAttr(new Attr.Builder("aLong").value("1976").build()). + plainAttr(new Attr.Builder("ctype").value("membership type").build()). + build()); userTO = updateUser(userUR).getEntity(); @@ -142,9 +142,8 @@ public void misc() throws JsonProcessingException { userUR = new UserUR(); userUR.setKey(userTO.getKey()); - membershipPatch = new MembershipUR.Builder(membership.getGroupKey()). - operation(PatchOperation.DELETE).build(); - userUR.getMemberships().add(membershipPatch); + userUR.getMemberships().add(new MembershipUR.Builder(membership.getGroupKey()). + operation(PatchOperation.DELETE).build()); userTO = updateUser(userUR).getEntity(); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java new file mode 100644 index 00000000000..73da3ef217c --- /dev/null +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java @@ -0,0 +1,171 @@ +/* + * 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.fit.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.AnyObjectCR; +import org.apache.syncope.common.lib.request.AnyObjectUR; +import org.apache.syncope.common.lib.request.RelationshipUR; +import org.apache.syncope.common.lib.request.UserCR; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.to.RelationshipTO; +import org.apache.syncope.common.lib.to.RelationshipTypeTO; +import org.apache.syncope.common.lib.to.TypeExtensionTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.fit.AbstractITCase; +import org.junit.jupiter.api.Test; + +public class RelationshipITCase extends AbstractITCase { + + @Test + public void unlimitedRelationships() { + AnyObjectCR anyObjectCR = AnyObjectITCase.getSample("unlimited1"); + anyObjectCR.setRealm("/even/two"); + anyObjectCR.getResources().clear(); + AnyObjectTO left = createAnyObject(anyObjectCR).getEntity(); + + anyObjectCR = AnyObjectITCase.getSample("unlimited2"); + anyObjectCR.setRealm(SyncopeConstants.ROOT_REALM); + anyObjectCR.getResources().clear(); + anyObjectCR.getRelationships().add(new RelationshipTO.Builder("inclusion"). + otherEnd(left.getType(), left.getKey()).build()); + AnyObjectTO right = createAnyObject(anyObjectCR).getEntity(); + + assertEquals(1, right.getRelationships().size()); + assertEquals(left.getKey(), right.getRelationships().getFirst().getOtherEndKey()); + + AnyObjectUR anyObjectUR = new AnyObjectUR.Builder(left.getKey()). + relationship(new RelationshipUR.Builder("inclusion"). + otherEnd(right.getType(), right.getKey()).build()).build(); + left = updateAnyObject(anyObjectUR).getEntity(); + assertEquals(2, left.getRelationships().size()); + assertTrue(left.getRelationships().stream().anyMatch(r -> right.getKey().equals(r.getOtherEndKey()))); + } + + @Test + public void relationshipWithAttr() { + // first add type extension to neighborhood + RelationshipTypeTO relTypeTO = RELATIONSHIP_TYPE_SERVICE.read("neighborhood"); + + if (relTypeTO.getTypeExtension(AnyTypeKind.USER.name()).isEmpty()) { + TypeExtensionTO typeExt = new TypeExtensionTO(); + typeExt.setAnyType(AnyTypeKind.USER.name()); + typeExt.getAuxClasses().add("other"); + relTypeTO.getTypeExtensions().add(typeExt); + + RELATIONSHIP_TYPE_SERVICE.update(relTypeTO); + relTypeTO = RELATIONSHIP_TYPE_SERVICE.read("neighborhood"); + } + assertEquals("other", relTypeTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().getFirst()); + + // then add relationship with attribute + UserCR userCR = UserITCase.getUniqueSample("relationshipWithAttr@syncope.apache.org"); + userCR.getRelationships().add(new RelationshipTO.Builder("neighborhood"). + otherEnd("PRINTER", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). + plainAttr(new Attr.Builder("obscure").value("testvalue3").build()). + build()); + + UserTO user = createUser(userCR).getEntity(); + + RelationshipTO rel = user.getRelationship("neighborhood", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").orElseThrow(); + assertEquals(1, rel.getPlainAttrs().size()); + assertEquals(1, rel.getPlainAttr("obscure").orElseThrow().getValues().size()); + assertEquals(1, rel.getDerAttrs().size()); + assertEquals(1, rel.getDerAttr("noschema").orElseThrow().getValues().size()); + } + + @Test + public void issueSYNCOPE1686() { + // Create printers + AnyObjectCR printer1CR = AnyObjectITCase.getSample("printer1"); + printer1CR.getResources().clear(); + String key1 = createAnyObject(printer1CR).getEntity().getKey(); + + AnyObjectCR printer2CR = AnyObjectITCase.getSample("printer2"); + printer2CR.getResources().clear(); + String key2 = createAnyObject(printer2CR).getEntity().getKey(); + + AnyObjectCR printer3CR = AnyObjectITCase.getSample("printer3"); + printer3CR.getResources().clear(); + String key3 = createAnyObject(printer3CR).getEntity().getKey(); + + // Add relationships: printer1 -> printer2 and printer2 -> printer3 + AnyObjectUR relationship1To2 = new AnyObjectUR.Builder(key1) + .relationship(new RelationshipUR.Builder("inclusion").otherEnd(PRINTER, key2).build()) + .build(); + AnyObjectUR relationship2To3 = new AnyObjectUR.Builder(key2) + .relationship(new RelationshipUR.Builder("inclusion").otherEnd(PRINTER, key3).build()) + .build(); + + updateAnyObject(relationship1To2); + updateAnyObject(relationship2To3); + + // Read updated printers + AnyObjectTO printer1 = ANY_OBJECT_SERVICE.read(key1); + AnyObjectTO printer2 = ANY_OBJECT_SERVICE.read(key2); + AnyObjectTO printer3 = ANY_OBJECT_SERVICE.read(key3); + + // Verify relationships for printer1 + assertEquals(1, printer1.getRelationships().size()); + RelationshipTO rel1 = printer1.getRelationships().getFirst(); + assertEquals(RelationshipTO.End.LEFT, rel1.getEnd()); + assertEquals(printer2.getKey(), rel1.getOtherEndKey()); + assertEquals(printer2.getType(), rel1.getOtherEndType()); + assertEquals(printer2.getName(), rel1.getOtherEndName()); + + // Verify relationships for printer2 + assertEquals(2, printer2.getRelationships().size()); + assertTrue(printer2.getRelationships().stream() + .anyMatch(r -> r.getEnd() == RelationshipTO.End.LEFT + && printer3.getKey().equals(r.getOtherEndKey()) + && printer3.getType().equals(r.getOtherEndType()) + && printer3.getName().equals(r.getOtherEndName()))); + assertTrue(printer2.getRelationships().stream() + .anyMatch(r -> r.getEnd() == RelationshipTO.End.RIGHT + && printer1.getKey().equals(r.getOtherEndKey()) + && printer1.getType().equals(r.getOtherEndType()) + && printer1.getName().equals(r.getOtherEndName()))); + + // Verify relationships for printer3 + assertEquals(1, printer3.getRelationships().size()); + RelationshipTO rel3 = printer3.getRelationships().getFirst(); + assertEquals(RelationshipTO.End.RIGHT, rel3.getEnd()); + assertEquals(printer2.getKey(), rel3.getOtherEndKey()); + assertEquals(printer2.getType(), rel3.getOtherEndType()); + assertEquals(printer2.getName(), rel3.getOtherEndName()); + + // Test invalid relationship with End.RIGHT + AnyObjectCR printer4CR = AnyObjectITCase.getSample("printer4"); + printer4CR.getResources().clear(); + printer4CR.getRelationships().add( + new RelationshipTO.Builder("inclusion", RelationshipTO.End.RIGHT).otherEnd(PRINTER, key1).build()); + + SyncopeClientException e = assertThrows(SyncopeClientException.class, () -> createAnyObject(printer4CR)); + assertEquals(ClientExceptionType.InvalidRelationship, e.getType()); + assertTrue(e.getMessage().contains("Relationships shall be created or updated only from their left end")); + } +} From 5cbe7eff7798f9883c0da6e421a5c0cd39dfb52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Tue, 16 Dec 2025 13:07:27 +0100 Subject: [PATCH 2/7] Fixing Neo4j --- .../syncope/client/console/pages/Realms.java | 51 ++++++++++--------- .../syncope/client/console/panels/Realm.java | 4 +- .../console/panels/RealmChoicePanel.java | 29 +++-------- .../neo4j/dao/repo/GroupRepoExtImpl.java | 3 +- .../entity/AbstractGroupableRelatable.java | 27 ++++++---- .../neo4j/entity/AbstractRelatable.java | 30 +++++++++-- .../neo4j/entity/AbstractRelationship.java | 2 +- .../entity/anyobject/Neo4jAnyObject.java | 12 ----- .../neo4j/entity/user/Neo4jUser.java | 12 ----- 9 files changed, 83 insertions(+), 87 deletions(-) diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java index 7839d160b9e..89b187f0c28 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java @@ -150,28 +150,31 @@ public RealmChoicePanel getRealmChoicePanel() { public void onEvent(final IEvent event) { super.onEvent(event); - if (event.getPayload() instanceof ChosenRealm) { - @SuppressWarnings("unchecked") - ChosenRealm choosenRealm = ChosenRealm.class.cast(event.getPayload()); - updateRealmContent(choosenRealm.getObj(), 0); - choosenRealm.getTarget().add(content); - } else if (event.getPayload() instanceof AjaxWizard.NewItemEvent newItemEvent) { - WizardModalPanel modalPanel = newItemEvent.getModalPanel(); - - if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) { - final IModel model = new CompoundPropertyModel<>(modalPanel.getItem()); - templateModal.setFormModel(model); - templateModal.header(newItemEvent.getTitleModel()); - newItemEvent.getTarget().ifPresent(t -> t.add(templateModal.setContent(modalPanel))); - templateModal.show(true); - } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { - newItemEvent.getTarget().ifPresent(templateModal::close); - } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { - SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); - newItemEvent.getTarget().ifPresent(t -> { - ((BasePage) getPage()).getNotificationPanel().refresh(t); - templateModal.close(t); - }); + switch (event.getPayload()) { + case ChosenRealm chosenRealm -> { + updateRealmContent(chosenRealm.obj(), 0); + chosenRealm.target().add(content); + } + case AjaxWizard.NewItemEvent newItemEvent -> { + WizardModalPanel modalPanel = newItemEvent.getModalPanel(); + + if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) { + final IModel model = new CompoundPropertyModel<>(modalPanel.getItem()); + templateModal.setFormModel(model); + templateModal.header(newItemEvent.getTitleModel()); + newItemEvent.getTarget().ifPresent(t -> t.add(templateModal.setContent(modalPanel))); + templateModal.show(true); + } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { + newItemEvent.getTarget().ifPresent(templateModal::close); + } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + newItemEvent.getTarget().ifPresent(t -> { + ((BasePage) getPage()).getNotificationPanel().refresh(t); + templateModal.close(t); + }); + } + } + default -> { } } } @@ -200,12 +203,12 @@ protected void onClickTemplate(final AjaxRequestTarget target) { @Override protected void setWindowClosedReloadCallback(final BaseModal modal) { modal.setWindowClosedCallback(target -> { - if (modal.getContent() instanceof ResultPanel rp) { + if (modal.getContent() instanceof final ResultPanel rp) { RealmTO newRealmTO = RealmTO.class.cast(ProvisioningResult.class.cast(rp.getResult()).getEntity()); // reload realmChoicePanel label too - SYNCOPE-1151 target.add(realmChoicePanel.reloadRealmTree(target, Model.of(newRealmTO))); realmChoicePanel.setCurrentRealm(newRealmTO); - send(Realms.this, Broadcast.DEPTH, new ChosenRealm<>(newRealmTO, target)); + send(Realms.this, Broadcast.DEPTH, new ChosenRealm(newRealmTO, target)); } else { target.add(realmChoicePanel.reloadRealmTree(target)); } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java index 3dace52aa6a..d3b2e08e748 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java @@ -120,7 +120,7 @@ protected List buildTabList(final PageReference pageRef) { tabs.add(new RealmDetailsTabPanel()); AnyLayout anyLayout = AnyLayoutUtils.fetch(roleRestClient, anyTypes.stream().map(AnyTypeTO::getKey).toList()); - for (AnyTypeTO anyType : anyTypes) { + anyTypes.forEach(anyType -> { tabs.add(new ITabComponent( new ResourceModel("anyType." + anyType.getKey(), anyType.getKey()), String.format("%s_SEARCH", anyType.getKey())) { @@ -139,7 +139,7 @@ public boolean isVisible() { isActionAuthorized(this, RENDER); } }); - } + }); return tabs; } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java index 401fe876772..48b79686d92 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java @@ -73,6 +73,10 @@ public class RealmChoicePanel extends Panel { + public record ChosenRealm(RealmTO obj, AjaxRequestTarget target) { + + } + private static final long serialVersionUID = -1100228004207271270L; protected static final String SEARCH_REALMS = "searchRealms"; @@ -124,8 +128,7 @@ protected List> load() { Stream> full; if (fullRealmsTree) { full = map.values().stream(). - map(realmTOListPair -> - Pair.of(realmTOListPair.getLeft().getFullPath(), realmTOListPair.getKey())). + map(pair -> Pair.of(pair.getLeft().getFullPath(), pair.getLeft())). sorted(Comparator.comparing(Pair::getLeft)); } else { full = map.entrySet().stream(). @@ -250,7 +253,7 @@ protected void chooseRealm(final RealmTO realm, final AjaxRequestTarget target) model.setObject(realm); setBreadcrumb(realm); target.add(container); - send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm<>(realm, target)); + send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm(realm, target)); } public void reloadRealmsTree() { @@ -512,26 +515,6 @@ public RealmTO moveToParentRealm(final String key) { return null; } - public static class ChosenRealm { - - protected final AjaxRequestTarget target; - - protected final T obj; - - public ChosenRealm(final T obj, final AjaxRequestTarget target) { - this.obj = obj; - this.target = target; - } - - public T getObj() { - return obj; - } - - public AjaxRequestTarget getTarget() { - return target; - } - } - public List getLinks() { return links; } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java index 036a538ac46..57a3e312b9b 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java @@ -444,7 +444,8 @@ public void delete(final Group group) { Neo4jGroup.NODE, group.getKey()); - cascadeDelete(Neo4jGroupTypeExtension.NODE, + cascadeDelete( + Neo4jGroupTypeExtension.NODE, Neo4jGroup.NODE, group.getKey()); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java index 1cefdf824a2..f718ed72a77 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java @@ -38,19 +38,28 @@ public abstract class AbstractGroupableRelatable< protected abstract List> memberships(); + @Override + public boolean add(final PlainAttr attr) { + if (attr.getMembership() != null) { + return memberships().stream(). + filter(m -> m.getKey().equals(attr.getMembership())).findFirst(). + map(m -> m.add(attr)). + orElse(false); + } + + return super.add(attr); + } + @Override public boolean remove(final PlainAttr attr) { - if (attr.getMembership() == null && attr.getRelationship() == null) { - return plainAttrs().put(attr.getSchema(), null) != null; + if (attr.getMembership() != null) { + return memberships().stream(). + filter(m -> m.getKey().equals(attr.getMembership())).findFirst(). + map(m -> m.plainAttrs().put(attr.getSchema(), null) != null). + orElse(false); } - return memberships().stream(). - filter(m -> m.getKey().equals(attr.getMembership())).findFirst(). - map(membership -> membership.plainAttrs().put(attr.getSchema(), null) != null). - or(() -> relationships().stream(). - filter(r -> r.getKey().equals(attr.getRelationship())).findFirst(). - map(relationship -> relationship.plainAttrs().put(attr.getSchema(), null) != null)). - orElse(false); + return super.remove(attr); } @Override diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java index 9d90aec9546..c9cf0c5d145 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java @@ -36,19 +36,43 @@ public abstract class AbstractRelatable< private static final long serialVersionUID = -2269285197388729673L; protected abstract List> relationships(); + + @Override + public boolean add(final PlainAttr attr) { + if (attr.getRelationship() != null) { + return relationships().stream(). + filter(r -> r.getKey().equals(attr.getRelationship())).findFirst(). + map(r -> r.add(attr)). + orElse(false); + } + + return super.add(attr); + } + + @Override + public boolean remove(final PlainAttr attr) { + if (attr.getRelationship() != null) { + return relationships().stream(). + filter(m -> m.getKey().equals(attr.getRelationship())).findFirst(). + map(r -> r.plainAttrs().put(attr.getSchema(), null) != null). + orElse(false); + } + return super.remove(attr); + } + @Override public Optional getPlainAttr(final String plainSchema, final Relationship relationship) { return relationships().stream(). filter(r -> r.getKey().equals(relationship.getKey())).findFirst(). - flatMap(m -> m.getPlainAttr(plainSchema)); + flatMap(r -> r.getPlainAttr(plainSchema)); } @Override public List getPlainAttrs(final Relationship relationship) { return relationships().stream(). filter(r -> r.getKey().equals(relationship.getKey())). - flatMap(m -> m.getPlainAttrs().stream()).toList(); + flatMap(r -> r.getPlainAttrs().stream()).toList(); } @Override @@ -60,7 +84,7 @@ public Optional getRelationship( && (otherEndKey.equals(relationship.getLeftEnd().getKey()) || otherEndKey.equals(relationship.getRightEnd().getKey()))).findFirst(); } - + @SuppressWarnings("unchecked") @Override public List getRelationships() { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java index 150ccd2c231..c9049d0023b 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelationship.java @@ -46,7 +46,7 @@ public Optional getPlainAttr(final String plainSchema) { } public boolean add(final PlainAttr attr) { - return getKey().equals(attr.getMembership()) + return getKey().equals(attr.getRelationship()) && plainAttrs().put(attr.getSchema(), attr) != null; } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java index a389b5fce68..39719b675de 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java @@ -120,18 +120,6 @@ public List getResources() { return resources; } - @Override - public boolean add(final PlainAttr attr) { - if (attr.getMembership() == null) { - return plainAttrs.put(attr.getSchema(), attr) != null; - } - - return memberships().stream(). - filter(membership -> membership.getKey().equals(attr.getMembership())).findFirst(). - map(membership -> membership.add(attr)). - orElse(false); - } - @Override public boolean add(final AnyTypeClass auxClass) { checkType(auxClass, Neo4jAnyTypeClass.class); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java index 41d152dbacc..7fdc63cccd9 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java @@ -235,18 +235,6 @@ public boolean canDecodeSecrets() { return this.cipherAlgorithm != null && this.cipherAlgorithm.isInvertible(); } - @Override - public boolean add(final PlainAttr attr) { - if (attr.getMembership() == null) { - return plainAttrs.put(attr.getSchema(), attr) != null; - } - - return memberships().stream(). - filter(membership -> membership.getKey().equals(attr.getMembership())).findFirst(). - map(membership -> membership.add(attr)). - orElse(false); - } - @Override public void generateToken(final int tokenLength, final int tokenExpireTime) { this.token = SecureRandomUtils.generateRandomPassword(tokenLength); From 236e2e7fcc94f77a14e1be16936b635dee9a8640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Tue, 16 Dec 2025 13:36:28 +0100 Subject: [PATCH 3/7] Fixing Oracle --- .../jpa/entity/AbstractTypeExtension.java | 19 +++------------ .../entity/JPARelationshipTypeExtension.java | 22 ++++++++++++++++++ .../entity/group/JPAGroupTypeExtension.java | 23 +++++++++++++++++++ .../test/resources/domains/MasterContent.xml | 6 ++--- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java index 107f1693cf7..d0cbfb827cd 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractTypeExtension.java @@ -18,14 +18,8 @@ */ package org.apache.syncope.core.persistence.jpa.entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.UniqueConstraint; -import java.util.ArrayList; import java.util.List; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; @@ -39,14 +33,7 @@ public abstract class AbstractTypeExtension extends AbstractGeneratedKeyEntity i @ManyToOne private JPAAnyType anyType; - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(joinColumns = - @JoinColumn(name = "typeExtension_id"), - inverseJoinColumns = - @JoinColumn(name = "anyTypeClass_id"), - uniqueConstraints = - @UniqueConstraint(columnNames = { "typeExtension_id", "anyTypeClass_id" })) - private List auxClasses = new ArrayList<>(); + protected abstract List auxClasses(); @Override public AnyType getAnyType() { @@ -62,11 +49,11 @@ public void setAnyType(final AnyType anyType) { @Override public boolean add(final AnyTypeClass auxClass) { checkType(auxClass, JPAAnyTypeClass.class); - return auxClasses.contains((JPAAnyTypeClass) auxClass) || auxClasses.add((JPAAnyTypeClass) auxClass); + return auxClasses().contains((JPAAnyTypeClass) auxClass) || auxClasses().add((JPAAnyTypeClass) auxClass); } @Override public List getAuxClasses() { - return auxClasses; + return auxClasses(); } } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java index ad9013edb1c..8a1a9243ad1 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARelationshipTypeExtension.java @@ -19,9 +19,15 @@ package org.apache.syncope.core.persistence.jpa.entity; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.List; import org.apache.syncope.core.persistence.api.entity.RelationshipType; import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; @@ -34,9 +40,25 @@ public class JPARelationshipTypeExtension extends AbstractTypeExtension implemen public static final String TABLE = "RelationshipTypeExtension"; + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable( + name = "RelTypeExtension_Class", + joinColumns = + @JoinColumn(name = "typeExtension_id"), + inverseJoinColumns = + @JoinColumn(name = "anyTypeClass_id"), + uniqueConstraints = + @UniqueConstraint(columnNames = { "typeExtension_id", "anyTypeClass_id" })) + private List auxClasses = new ArrayList<>(); + @ManyToOne private JPARelationshipType relationshipType; + @Override + protected List auxClasses() { + return auxClasses; + } + @Override public RelationshipType getRelationshipType() { return relationshipType; diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java index bfcf9173827..73799384c2d 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroupTypeExtension.java @@ -19,12 +19,19 @@ package org.apache.syncope.core.persistence.jpa.entity.group; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.List; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.jpa.entity.AbstractTypeExtension; +import org.apache.syncope.core.persistence.jpa.entity.JPAAnyTypeClass; @Entity @Table(name = JPAGroupTypeExtension.TABLE, uniqueConstraints = @@ -35,9 +42,25 @@ public class JPAGroupTypeExtension extends AbstractTypeExtension implements Grou public static final String TABLE = "GroupTypeExtension"; + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable( + name = "GroupTypeExtension_Class", + joinColumns = + @JoinColumn(name = "typeExtension_id"), + inverseJoinColumns = + @JoinColumn(name = "anyTypeClass_id"), + uniqueConstraints = + @UniqueConstraint(columnNames = { "typeExtension_id", "anyTypeClass_id" })) + private List auxClasses = new ArrayList<>(); + @ManyToOne private JPAGroup group; + @Override + protected List auxClasses() { + return auxClasses; + } + @Override public Group getGroup() { return group; diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml index 92e4a5784f6..9eaad9ba862 100644 --- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml @@ -395,7 +395,7 @@ under the License. creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/> - + - - + + Date: Tue, 16 Dec 2025 17:31:01 +0100 Subject: [PATCH 4/7] Reviewing IntAttrName --- .../core/provisioning/api/IntAttrName.java | 168 ++++++------- .../provisioning/api/IntAttrNameParser.java | 119 ++++----- .../api/IntAttrNameParserTest.java | 236 ++++++++---------- .../java/DefaultMappingManager.java | 187 +++++++------- .../java/ProvisioningContext.java | 2 - .../provisioning/java/data/AnyDataBinder.java | 6 +- .../java/data/RealmDataBinderImpl.java | 6 +- .../java/data/ResourceDataBinderImpl.java | 34 +-- .../DefaultNotificationManager.java | 40 +-- .../java/pushpull/InboundMatcher.java | 8 +- .../core/logic/oidc/OIDCUserManager.java | 10 +- .../data/OIDCC4UIProviderDataBinderImpl.java | 2 +- .../logic/saml2/SAML2SP4UIUserManager.java | 16 +- .../data/SAML2SP4UIIdPDataBinderImpl.java | 2 +- .../fit/core/PropagationTaskITCase.java | 12 +- .../concepts/externalresources.adoc | 21 +- 16 files changed, 396 insertions(+), 473 deletions(-) diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrName.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrName.java index e996b80b54e..2d99df2c15e 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrName.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrName.java @@ -21,125 +21,115 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.syncope.core.persistence.api.entity.Schema; public class IntAttrName { - private AnyTypeKind anyTypeKind; + public record SchemaInfo(Schema schema, SchemaType type) { - private String field; - - private SchemaType schemaType; - - private Schema schema; + } - private String enclosingGroup; + public record RelationshipInfo(String type, String anyObject) { - private String relatedUser; + } - private String relatedAnyObject; + protected static class Builder { - private String membershipOfGroup; + private final IntAttrName instance = new IntAttrName(); - private String relationshipType; + public Builder withField(final String field) { + instance.field = field; + return this; + } - private String relationshipAnyType; + public Builder withSchemaInfo(final SchemaInfo schemaInfo) { + instance.schemaInfo = schemaInfo; + return this; + } - public AnyTypeKind getAnyTypeKind() { - return anyTypeKind; - } + public Builder withExternalGroup(final String externalGroup) { + instance.externalGroup = externalGroup; + return this; + } - public void setAnyTypeKind(final AnyTypeKind anyTypeKind) { - this.anyTypeKind = anyTypeKind; - } + public Builder withExternalUser(final String externalUser) { + instance.externalUser = externalUser; + return this; + } - public String getField() { - return field; - } + public Builder withExternalAnyObject(final String externalAnyObject) { + instance.externalAnyObject = externalAnyObject; + return this; + } - public void setField(final String field) { - this.field = field; - } + public Builder withMembership(final String membership) { + instance.membership = membership; + return this; + } - public SchemaType getSchemaType() { - return schemaType; - } + public Builder withRelationship(final String type, final String anyObject) { + instance.relationshipInfo = new RelationshipInfo(type, anyObject); + return this; + } - public void setSchemaType(final SchemaType schemaType) { - this.schemaType = schemaType; + protected IntAttrName build() { + return instance; + } } - public Schema getSchema() { - return schema; - } + private String field; - public void setSchema(final Schema schemaName) { - this.schema = schemaName; - } + private SchemaInfo schemaInfo; - public String getEnclosingGroup() { - return enclosingGroup; - } + private String externalGroup; - public void setEnclosingGroup(final String enclosingGroup) { - this.enclosingGroup = enclosingGroup; - } + private String externalUser; - public String getRelatedUser() { - return relatedUser; - } + private String externalAnyObject; - public void setRelatedUser(final String relatedUser) { - this.relatedUser = relatedUser; - } + private String membership; - public String getRelatedAnyObject() { - return relatedAnyObject; - } + private RelationshipInfo relationshipInfo; - public void setRelatedAnyObject(final String relatedAnyObject) { - this.relatedAnyObject = relatedAnyObject; + public String getField() { + return field; } - public String getMembershipOfGroup() { - return membershipOfGroup; + public SchemaInfo getSchemaInfo() { + return schemaInfo; } - public void setMembershipOfGroup(final String membershipOfGroup) { - this.membershipOfGroup = membershipOfGroup; + public String getExternalGroup() { + return externalGroup; } - public String getRelationshipType() { - return relationshipType; + public String getExternalUser() { + return externalUser; } - public void setRelationshipType(final String relationshipType) { - this.relationshipType = relationshipType; + public String getExternalAnyObject() { + return externalAnyObject; } - public String getRelationshipAnyType() { - return relationshipAnyType; + public String getMembership() { + return membership; } - public void setRelationshipAnyType(final String relationshipAnyType) { - this.relationshipAnyType = relationshipAnyType; + public RelationshipInfo getRelationshipInfo() { + return relationshipInfo; } @Override public int hashCode() { return new HashCodeBuilder(). - append(anyTypeKind). append(field). - append(schemaType). - append(schema). - append(enclosingGroup). - append(relatedUser). - append(relatedAnyObject). - append(membershipOfGroup). - append(relationshipType). - append(relationshipAnyType). + append(schemaInfo). + append(externalGroup). + append(externalUser). + append(externalAnyObject). + append(membership). + append(relationshipInfo). build(); } @@ -156,32 +146,26 @@ public boolean equals(final Object obj) { } final IntAttrName other = (IntAttrName) obj; return new EqualsBuilder(). - append(anyTypeKind, other.anyTypeKind). append(field, other.field). - append(schemaType, other.schemaType). - append(schema, other.schema). - append(enclosingGroup, other.enclosingGroup). - append(relatedUser, other.relatedUser). - append(relatedAnyObject, other.relatedAnyObject). - append(membershipOfGroup, other.membershipOfGroup). - append(relationshipType, other.relationshipType). - append(relationshipAnyType, other.relationshipAnyType). + append(schemaInfo, other.schemaInfo). + append(externalGroup, other.externalGroup). + append(externalUser, other.externalUser). + append(externalAnyObject, other.externalAnyObject). + append(membership, other.membership). + append(relationshipInfo, other.relationshipInfo). build(); } @Override public String toString() { return new ToStringBuilder(this). - append(anyTypeKind). append(field). - append(schemaType). - append(schema). - append(enclosingGroup). - append(relatedUser). - append(relatedAnyObject). - append(membershipOfGroup). - append(relationshipType). - append(relationshipAnyType). + append(schemaInfo). + append(externalGroup). + append(externalUser). + append(externalAnyObject). + append(membership). + append(relationshipInfo). build(); } } diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java index 77a22e8f869..dd74006b1fd 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java @@ -36,19 +36,15 @@ @SuppressWarnings({ "squid:S4784", "squid:S3776" }) public class IntAttrNameParser { - protected record SchemaInfo(Schema schema, SchemaType schemaType) { - - } - protected static final String END_PATTERN = "\\]\\.(.+)"; - protected static final Pattern ENCLOSING_GROUP_PATTERN = Pattern.compile( + protected static final Pattern EXTERNAL_GROUP_PATTERN = Pattern.compile( "^groups\\[(" + Entity.ID_REGEX + ")" + END_PATTERN); - protected static final Pattern RELATED_USER_PATTERN = Pattern.compile( + protected static final Pattern EXTERNAL_USER_PATTERN = Pattern.compile( "^users\\[(" + Entity.ID_REGEX + ")" + END_PATTERN); - protected static final Pattern RELATED_ANY_OBJECT_PATTERN = Pattern.compile( + protected static final Pattern EXTERNAL_ANY_OBJECT_PATTERN = Pattern.compile( "^anyObjects\\[(" + Entity.ID_REGEX + ")" + END_PATTERN); protected static final Pattern MEMBERSHIP_PATTERN = Pattern.compile( @@ -81,94 +77,85 @@ public IntAttrNameParser( this.realmUtils = realmUtils; } - protected SchemaInfo find(final String key) { + protected IntAttrName.SchemaInfo find(final String key) { Schema schema = plainSchemaDAO.findById(key).orElse(null); if (schema == null) { schema = derSchemaDAO.findById(key).orElse(null); if (schema == null) { return null; } - return new SchemaInfo(schema, SchemaType.DERIVED); + return new IntAttrName.SchemaInfo(schema, SchemaType.DERIVED); } - return new SchemaInfo(schema, SchemaType.PLAIN); + return new IntAttrName.SchemaInfo(schema, SchemaType.PLAIN); } protected void setFieldOrSchemaName( final String fieldOrSchemaName, final AnyTypeKind anyTypeKind, - final IntAttrName result) { + final IntAttrName.Builder result) { anyUtilsFactory.getInstance(anyTypeKind).getField(fieldOrSchemaName).ifPresentOrElse( - field -> result.setField(fieldOrSchemaName), - () -> Optional.ofNullable(find(fieldOrSchemaName)).ifPresent(schemaInfo -> { - result.setSchemaType(schemaInfo.schemaType()); - result.setSchema(schemaInfo.schema()); - })); + field -> result.withField(fieldOrSchemaName), + () -> Optional.ofNullable(find(fieldOrSchemaName)).ifPresent(result::withSchemaInfo)); } @Transactional(readOnly = true) public IntAttrName parse(final String intAttrName, final AnyTypeKind provisionAnyTypeKind) throws ParseException { - IntAttrName result = new IntAttrName(); + IntAttrName.Builder result = new IntAttrName.Builder(); Matcher matcher = Pattern.compile(END_PATTERN).matcher(intAttrName); if (!matcher.matches() && !Strings.CS.containsAny(intAttrName, RESERVED_WORDS)) { - result.setAnyTypeKind(provisionAnyTypeKind); - setFieldOrSchemaName(intAttrName, result.getAnyTypeKind(), result); - } else { - matcher = ENCLOSING_GROUP_PATTERN.matcher(intAttrName); - if (matcher.matches()) { - result.setAnyTypeKind(AnyTypeKind.GROUP); - result.setEnclosingGroup(matcher.group(1)); - setFieldOrSchemaName(matcher.group(2), result.getAnyTypeKind(), result); - } else { - matcher = RELATED_ANY_OBJECT_PATTERN.matcher(intAttrName); - if (matcher.matches()) { - result.setAnyTypeKind(AnyTypeKind.ANY_OBJECT); - result.setRelatedAnyObject(matcher.group(1)); - setFieldOrSchemaName(matcher.group(2), result.getAnyTypeKind(), result); - } else { - matcher = MEMBERSHIP_PATTERN.matcher(intAttrName); - if (matcher.matches()) { - result.setAnyTypeKind(AnyTypeKind.USER); - result.setMembershipOfGroup(matcher.group(1)); - setFieldOrSchemaName(matcher.group(2), result.getAnyTypeKind(), result); - } else { - matcher = RELATED_USER_PATTERN.matcher(intAttrName); - if (matcher.matches()) { - result.setAnyTypeKind(AnyTypeKind.USER); - result.setRelatedUser(matcher.group(1)); - setFieldOrSchemaName(matcher.group(2), result.getAnyTypeKind(), result); - } else { - matcher = RELATIONSHIP_PATTERN.matcher(intAttrName); - if (matcher.matches()) { - result.setAnyTypeKind(AnyTypeKind.ANY_OBJECT); - result.setRelationshipType(matcher.group(1)); - result.setRelationshipAnyType(matcher.group(2)); - setFieldOrSchemaName(matcher.group(3), result.getAnyTypeKind(), result); - } else { - throw new ParseException("Unparsable expression: " + intAttrName, 0); - } - } - } - } - } + setFieldOrSchemaName(intAttrName, provisionAnyTypeKind, result); + return result.build(); + } + + matcher = EXTERNAL_GROUP_PATTERN.matcher(intAttrName); + if (matcher.matches()) { + result.withExternalGroup(matcher.group(1)); + setFieldOrSchemaName(matcher.group(2), AnyTypeKind.GROUP, result); + return result.build(); + } + + matcher = EXTERNAL_ANY_OBJECT_PATTERN.matcher(intAttrName); + if (matcher.matches()) { + result.withExternalAnyObject(matcher.group(1)); + setFieldOrSchemaName(matcher.group(2), AnyTypeKind.ANY_OBJECT, result); + return result.build(); + } + + matcher = MEMBERSHIP_PATTERN.matcher(intAttrName); + if (matcher.matches()) { + result.withMembership(matcher.group(1)); + setFieldOrSchemaName(matcher.group(2), AnyTypeKind.GROUP, result); + return result.build(); + } + + matcher = EXTERNAL_USER_PATTERN.matcher(intAttrName); + if (matcher.matches()) { + result.withExternalUser(matcher.group(1)); + setFieldOrSchemaName(matcher.group(2), AnyTypeKind.USER, result); + return result.build(); + } + + matcher = RELATIONSHIP_PATTERN.matcher(intAttrName); + if (matcher.matches()) { + result.withRelationship(matcher.group(1), matcher.group(2)); + setFieldOrSchemaName(matcher.group(3), AnyTypeKind.ANY_OBJECT, result); + return result.build(); } - return result; + throw new ParseException("Unparsable expression: " + intAttrName, 0); } - protected void setFieldOrSchemaName(final String fieldOrSchemaName, final IntAttrName result) { + protected void setFieldOrSchemaName(final String fieldOrSchemaName, final IntAttrName.Builder result) { realmUtils.getField(fieldOrSchemaName).ifPresentOrElse( - field -> result.setField(fieldOrSchemaName), - () -> Optional.ofNullable(find(fieldOrSchemaName)).ifPresent(schemaInfo -> { - result.setSchemaType(schemaInfo.schemaType()); - result.setSchema(schemaInfo.schema()); - })); + field -> result.withField(fieldOrSchemaName), + () -> Optional.ofNullable(find(fieldOrSchemaName)).ifPresent(result::withSchemaInfo)); } @Transactional(readOnly = true) public IntAttrName parse(final String intAttrName) throws ParseException { - IntAttrName result = new IntAttrName(); + IntAttrName.Builder result = new IntAttrName.Builder(); if (intAttrName.indexOf('.') == -1) { setFieldOrSchemaName(intAttrName, result); @@ -176,6 +163,6 @@ public IntAttrName parse(final String intAttrName) throws ParseException { throw new ParseException("Unparsable expression: " + intAttrName, 0); } - return result; + return result.build(); } } diff --git a/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java index 98412951d87..3d23436ec61 100644 --- a/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java +++ b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java @@ -138,55 +138,45 @@ public void initMocks() throws NoSuchFieldException { public void ownFields() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("key", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNotNull(intAttrName.getField()); assertEquals("key", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("name", AnyTypeKind.GROUP); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind()); assertNotNull(intAttrName.getField()); assertEquals("name", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("userOwner", AnyTypeKind.GROUP); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind()); assertNotNull(intAttrName.getField()); assertEquals("userOwner", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("name", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); Object nullObj = null; int expected = new HashCodeBuilder(). - append(AnyTypeKind.USER).append(nullObj).append(nullObj).append(nullObj).append(nullObj). - append(nullObj).append(nullObj).append(nullObj).append(nullObj).append(nullObj). + append(nullObj).append(nullObj).append(nullObj). + append(nullObj).append(nullObj).append(nullObj).append(nullObj). build(); assertEquals(expected, intAttrName.hashCode()); IntAttrName intAttrName2 = intAttrNameParser.parse("email", AnyTypeKind.USER); @@ -195,122 +185,107 @@ public void ownFields() throws ParseException { assertTrue(intAttrName.equals(intAttrName)); String toString = intAttrName.toString(); assertTrue(toString.startsWith("org.apache.syncope.core.provisioning.api.IntAttrName")); - assertTrue(toString.endsWith("[USER,,,,,,,,,]")); + assertTrue(toString.endsWith("[,,,,,,]")); } @Test public void ownSchema() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("email", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("email", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.PLAIN, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof PlainSchema); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("email", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.PLAIN, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof PlainSchema); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("cn", AnyTypeKind.ANY_OBJECT); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("cn", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof DerSchema); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("cn", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.DERIVED, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof DerSchema); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); } @Test - public void enclosingGroup() throws ParseException { + public void externalGroup() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("groups[readers].cn", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("cn", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof DerSchema); - assertEquals("readers", intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("cn", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.DERIVED, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof DerSchema); + assertEquals("readers", intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); } @Test - public void relatedUser() throws ParseException { + public void externalUser() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("users[bellini].firstname", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("firstname", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.PLAIN, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof PlainSchema); - assertEquals("bellini", intAttrName.getRelatedUser()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); + assertEquals("firstname", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.PLAIN, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof PlainSchema); + assertEquals("bellini", intAttrName.getExternalUser()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); } @Test - public void relatedAnyObject() throws ParseException { + public void externalAnyObject() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("anyObjects[hp].name", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind()); assertEquals("name", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertEquals("hp", intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertEquals("hp", intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); } @Test public void membership() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("memberships[top].cn", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("cn", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof DerSchema); - assertNull(intAttrName.getEnclosingGroup()); - assertEquals("top", intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("cn", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.DERIVED, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof DerSchema); + assertNull(intAttrName.getExternalGroup()); + assertEquals("top", intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); } @Test public void relationship() throws ParseException { - IntAttrName intAttrName = intAttrNameParser.parse( - "relationships[inclusion][PRINTER].location", AnyTypeKind.USER); + IntAttrName intAttrName = intAttrNameParser.parse("relationships[inclusion][hp].location", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("location", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.PLAIN, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof PlainSchema); - assertEquals("inclusion", intAttrName.getRelationshipType()); - assertEquals("PRINTER", intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("location", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.PLAIN, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof PlainSchema); + assertEquals("inclusion", intAttrName.getRelationshipInfo().type()); + assertEquals("hp", intAttrName.getRelationshipInfo().anyObject()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getExternalUser()); } @Test @@ -327,45 +302,37 @@ public void invalid() { public void realm() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("key"); assertNotNull(intAttrName); - assertNull(intAttrName.getAnyTypeKind()); assertNotNull(intAttrName.getField()); assertEquals("key", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("name"); assertNotNull(intAttrName); - assertNull(intAttrName.getAnyTypeKind()); assertNotNull(intAttrName.getField()); assertEquals("name", intAttrName.getField()); - assertNull(intAttrName.getSchema()); - assertNull(intAttrName.getSchemaType()); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertNull(intAttrName.getSchemaInfo()); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); intAttrName = intAttrNameParser.parse("index"); assertNotNull(intAttrName); - assertNull(intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("index", intAttrName.getSchema().getKey()); - assertEquals(SchemaType.PLAIN, intAttrName.getSchemaType()); - assertTrue(intAttrName.getSchema() instanceof PlainSchema); - assertNull(intAttrName.getEnclosingGroup()); - assertNull(intAttrName.getMembershipOfGroup()); - assertNull(intAttrName.getRelatedAnyObject()); - assertNull(intAttrName.getRelationshipAnyType()); - assertNull(intAttrName.getRelationshipType()); - assertNull(intAttrName.getRelatedUser()); + assertEquals("index", intAttrName.getSchemaInfo().schema().getKey()); + assertEquals(SchemaType.PLAIN, intAttrName.getSchemaInfo().type()); + assertTrue(intAttrName.getSchemaInfo().schema() instanceof PlainSchema); + assertNull(intAttrName.getExternalGroup()); + assertNull(intAttrName.getMembership()); + assertNull(intAttrName.getExternalAnyObject()); + assertNull(intAttrName.getRelationshipInfo()); + assertNull(intAttrName.getExternalUser()); try { intAttrNameParser.parse("groups[readers].cn"); @@ -375,12 +342,11 @@ public void realm() throws ParseException { } } - @Test + @Test public void issueSYNCOPE1894() throws ParseException { IntAttrName intAttrName = intAttrNameParser.parse("user.valueWithDot", AnyTypeKind.USER); assertNotNull(intAttrName); - assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind()); assertNull(intAttrName.getField()); - assertEquals("user.valueWithDot", intAttrName.getSchema().getKey()); + assertEquals("user.valueWithDot", intAttrName.getSchemaInfo().schema().getKey()); } } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java index 76b88c6baf4..d447634095f 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java @@ -49,14 +49,12 @@ import org.apache.syncope.common.lib.types.AttrSchemaType; import org.apache.syncope.core.persistence.api.EncryptorManager; import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; -import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.ImplementationDAO; import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO; import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.Any; -import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.DerSchema; import org.apache.syncope.core.persistence.api.entity.ExternalResource; import org.apache.syncope.core.persistence.api.entity.Groupable; @@ -69,7 +67,6 @@ import org.apache.syncope.core.persistence.api.entity.Relatable; import org.apache.syncope.core.persistence.api.entity.Relationship; import org.apache.syncope.core.persistence.api.entity.RelationshipType; -import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.user.Account; import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; @@ -169,8 +166,6 @@ protected static PlainAttrValue clonePlainAttrValue(final PlainAttrValue src) { return dst; } - protected final AnyTypeDAO anyTypeDAO; - protected final UserDAO userDAO; protected final AnyObjectDAO anyObjectDAO; @@ -192,7 +187,6 @@ protected static PlainAttrValue clonePlainAttrValue(final PlainAttrValue src) { protected final JexlTools jexlTools; public DefaultMappingManager( - final AnyTypeDAO anyTypeDAO, final UserDAO userDAO, final AnyObjectDAO anyObjectDAO, final GroupDAO groupDAO, @@ -204,7 +198,6 @@ public DefaultMappingManager( final EncryptorManager encryptorManager, final JexlTools jexlTools) { - this.anyTypeDAO = anyTypeDAO; this.userDAO = userDAO; this.anyObjectDAO = anyObjectDAO; this.groupDAO = groupDAO; @@ -511,9 +504,10 @@ public PreparedAttr prepareAttr( return null; } - AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema - ? intAttrName.getSchema().getType() - : AttrSchemaType.String; + AttrSchemaType schemaType = Optional.ofNullable(intAttrName.getSchemaInfo()). + filter(schemaInfo -> schemaInfo.schema() instanceof PlainSchema). + map(schemaInfo -> schemaInfo.schema().getType()). + orElse(AttrSchemaType.String); IntValues intValues = getIntValues( resource, provision, item, intAttrName, schemaType, any, usernameAccountGetter, plainAttrGetter); @@ -528,27 +522,30 @@ public PreparedAttr prepareAttr( * ClassType {} * AttrSchemaType {} * Values {}""", - item, intAttrName.getSchema(), schemaType.getType().getName(), schemaType, values); + item, intAttrName.getSchemaInfo(), schemaType.getType().getName(), schemaType, values); List objValues = new ArrayList<>(); for (PlainAttrValue value : values) { - if (intAttrName.getSchema() instanceof PlainSchema schema && schemaType == AttrSchemaType.Encrypted) { + if (schemaType == AttrSchemaType.Encrypted + && intAttrName.getSchemaInfo().schema() instanceof PlainSchema schema) { + String decoded = null; try { decoded = encryptorManager.getInstance(schema.getSecretKey()). decode(value.getStringValue(), schema.getCipherAlgorithm()); } catch (Exception e) { LOG.warn("Could not decode value for {} with algorithm {}", - intAttrName.getSchema(), schema.getCipherAlgorithm(), e); + intAttrName.getSchemaInfo(), schema.getCipherAlgorithm(), e); } objValues.add(Optional.ofNullable(decoded).orElse(value.getStringValue())); } else if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) { objValues.add(value.getValue()); } else { - PlainSchema plainSchema = intAttrName.getSchema() instanceof final PlainSchema schema - ? schema - : null; + PlainSchema plainSchema = Optional.ofNullable(intAttrName.getSchemaInfo()). + map(IntAttrName.SchemaInfo::schema). + filter(PlainSchema.class::isInstance).map(PlainSchema.class::cast). + orElse(null); if (plainSchema == null || plainSchema.getType() != schemaType) { objValues.add(value.getValueAsString(schemaType)); } else { @@ -596,9 +593,10 @@ public PreparedAttr prepareAttr( return null; } - AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema - ? intAttrName.getSchema().getType() - : AttrSchemaType.String; + AttrSchemaType schemaType = Optional.ofNullable(intAttrName.getSchemaInfo()). + filter(schemaInfo -> schemaInfo.schema() instanceof PlainSchema). + map(schemaInfo -> schemaInfo.schema().getType()). + orElse(AttrSchemaType.String); IntValues intValues = getIntValues(resource, item, intAttrName, schemaType, realm); schemaType = intValues.attrSchemaType(); @@ -612,27 +610,30 @@ public PreparedAttr prepareAttr( * ClassType {} * AttrSchemaType {} * Values {}""", - item, intAttrName.getSchema(), schemaType.getType().getName(), schemaType, values); + item, intAttrName.getSchemaInfo(), schemaType.getType().getName(), schemaType, values); List objValues = new ArrayList<>(); for (PlainAttrValue value : values) { - if (intAttrName.getSchema() instanceof PlainSchema schema && schemaType == AttrSchemaType.Encrypted) { + if (schemaType == AttrSchemaType.Encrypted + && intAttrName.getSchemaInfo().schema() instanceof PlainSchema schema) { + String decoded = null; try { decoded = encryptorManager.getInstance(schema.getSecretKey()). decode(value.getStringValue(), schema.getCipherAlgorithm()); } catch (Exception e) { LOG.warn("Could not decode value for {} with algorithm {}", - intAttrName.getSchema(), schema.getCipherAlgorithm(), e); + intAttrName.getSchemaInfo(), schema.getCipherAlgorithm(), e); } objValues.add(Optional.ofNullable(decoded).orElse(value.getStringValue())); } else if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) { objValues.add(value.getValue()); } else { - PlainSchema plainSchema = intAttrName.getSchema() instanceof final PlainSchema schema - ? schema - : null; + PlainSchema plainSchema = Optional.ofNullable(intAttrName.getSchemaInfo()). + map(IntAttrName.SchemaInfo::schema). + filter(PlainSchema.class::isInstance).map(PlainSchema.class::cast). + orElse(null); if (plainSchema == null || plainSchema.getType() != schemaType) { objValues.add(value.getValueAsString(schemaType)); } else { @@ -676,65 +677,41 @@ public IntValues getIntValues( LOG.debug("Get internal values for {} as '{}' on {}", any, item.getIntAttrName(), resource); List references = new ArrayList<>(); - if (intAttrName.getEnclosingGroup() == null - && intAttrName.getRelatedAnyObject() == null - && intAttrName.getRelationshipAnyType() == null - && intAttrName.getRelationshipType() == null - && intAttrName.getRelatedUser() == null) { + if (intAttrName.getExternalGroup() == null + && intAttrName.getExternalAnyObject() == null + && intAttrName.getExternalUser() == null) { references.add(any); } - Membership membership = null; - - if (intAttrName.getEnclosingGroup() != null) { - Group group = groupDAO.findByName(intAttrName.getEnclosingGroup()).orElse(null); - if (group == null - || any instanceof User - ? !userDAO.findAllGroupKeys((User) any).contains(group.getKey()) - : any instanceof final AnyObject anyObject - ? !anyObjectDAO.findAllGroupKeys(anyObject).contains(group.getKey()) - : false) { - LOG.warn("No (dyn) membership for {} in {}, ignoring", intAttrName.getEnclosingGroup(), any); - } else { - references.add(group); - } - } else if (intAttrName.getRelatedUser() != null) { - User user = userDAO.findByUsername(intAttrName.getRelatedUser()).orElse(null); - if (user == null || user.getRelationships(any.getKey()).isEmpty()) { - LOG.warn("No relationship for {} in {}, ignoring", intAttrName.getRelatedUser(), any); - } else if (any.getType().getKind() == AnyTypeKind.USER) { - LOG.warn("Users cannot have relationship with other users, ignoring"); - } else { - references.add(user); - } - } else if (intAttrName.getRelatedAnyObject() != null && any instanceof Relatable relatable) { - AnyObject anyObject = anyObjectDAO.findById(intAttrName.getRelatedAnyObject()).orElse(null); - if (anyObject == null || relatable.getRelationships(anyObject.getKey()).isEmpty()) { - LOG.warn("No relationship for {} in {}, ignoring", intAttrName.getRelatedAnyObject(), relatable); - } else { - references.add(anyObject); - } - } else if (intAttrName.getRelationshipAnyType() != null && intAttrName.getRelationshipType() != null - && any instanceof Relatable relatable) { + Relationship relationship = null; + Membership membership = null; + if (intAttrName.getExternalUser() != null) { + userDAO.findByUsername(intAttrName.getExternalUser()).ifPresentOrElse( + references::add, + () -> LOG.warn("Could not find user {}, ignoring", intAttrName.getExternalUser())); + } else if (intAttrName.getExternalGroup() != null) { + groupDAO.findByName(intAttrName.getExternalGroup()).ifPresentOrElse( + references::add, + () -> LOG.warn("Could not find group {}, ignoring", intAttrName.getExternalGroup())); + } else if (intAttrName.getExternalAnyObject() != null) { + references.addAll(anyObjectDAO.findByName(intAttrName.getExternalAnyObject())); + } else if (intAttrName.getMembership() != null && any instanceof Groupable groupable) { + membership = groupDAO.findByName(intAttrName.getMembership()). + flatMap(group -> groupable.getMembership(group.getKey())). + orElse(null); + } else if (intAttrName.getRelationshipInfo() != null && any instanceof Relatable relatable) { RelationshipType relationshipType = relationshipTypeDAO.findById( - intAttrName.getRelationshipType()).orElse(null); - AnyType anyType = anyTypeDAO.findById(intAttrName.getRelationshipAnyType()).orElse(null); - if (relationshipType == null || relatable.getRelationships(relationshipType).isEmpty()) { - LOG.warn("No relationship for type {} in {}, ignoring", intAttrName.getRelationshipType(), relatable); - } else if (anyType == null) { - LOG.warn("No anyType {}, ignoring", intAttrName.getRelationshipAnyType()); + intAttrName.getRelationshipInfo().type()).orElse(null); + if (relationshipType == null) { + LOG.warn("Could not find relationship type {}, ignoring", intAttrName.getRelationshipInfo().type()); } else { - references.addAll(relatable.getRelationships(relationshipType).stream(). - filter(relationship -> anyType.equals(relationship.getRightEnd().getType())). - map(Relationship::getRightEnd). - toList()); + relationship = anyObjectDAO.findByName( + relationshipType.getRightEndAnyType().getKey(), intAttrName.getRelationshipInfo().anyObject()). + flatMap(otherEnd -> relatable.getRelationship(relationshipType, otherEnd.getKey())). + orElse(null); } - } else if (intAttrName.getMembershipOfGroup() != null && any instanceof Groupable groupable) { - membership = groupDAO.findByName(intAttrName.getMembershipOfGroup()). - flatMap(group -> groupable.getMembership(group.getKey())). - orElse(null); } if (references.isEmpty()) { LOG.warn("Could not determine the reference instance for {}", item.getIntAttrName()); @@ -828,13 +805,17 @@ public IntValues getIntValues( } } // ignore - } else if (intAttrName.getSchemaType() != null) { - switch (intAttrName.getSchemaType()) { + } else if (intAttrName.getSchemaInfo() != null) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { - PlainAttr attr = membership == null - ? plainAttrGetter.apply(ref, intAttrName.getSchema().getKey()) - : ((Groupable) ref).getPlainAttr( - intAttrName.getSchema().getKey(), membership).orElse(null); + PlainAttr attr = membership == null && relationship == null + ? plainAttrGetter.apply(ref, intAttrName.getSchemaInfo().schema().getKey()) + : membership == null + ? ((Relatable) ref).getPlainAttr( + intAttrName.getSchemaInfo().schema().getKey(), relationship). + orElse(null) + : ((Groupable) ref).getPlainAttr( + intAttrName.getSchemaInfo().schema().getKey(), membership).orElse(null); if (attr != null) { if (attr.getUniqueValue() != null) { values.add(clonePlainAttrValue(attr.getUniqueValue())); @@ -845,10 +826,12 @@ public IntValues getIntValues( } case DERIVED -> { - DerSchema derSchema = (DerSchema) intAttrName.getSchema(); - String derValue = membership == null + DerSchema derSchema = (DerSchema) intAttrName.getSchemaInfo().schema(); + String derValue = membership == null && relationship == null ? derAttrHandler.getValue(ref, derSchema) - : derAttrHandler.getValue(ref, membership, derSchema); + : membership == null + ? derAttrHandler.getValue(ref, relationship, derSchema) + : derAttrHandler.getValue(ref, membership, derSchema); if (derValue != null) { PlainAttrValue attrValue = new PlainAttrValue(); attrValue.setStringValue(derValue); @@ -914,10 +897,10 @@ public IntValues getIntValues( default -> { } } - } else if (intAttrName.getSchemaType() != null) { - switch (intAttrName.getSchemaType()) { + } else if (intAttrName.getSchemaInfo() != null) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { - realm.getPlainAttr(intAttrName.getSchema().getKey()).ifPresent(attr -> { + realm.getPlainAttr(intAttrName.getSchemaInfo().schema().getKey()).ifPresent(attr -> { if (attr.getUniqueValue() != null) { values.add(clonePlainAttrValue(attr.getUniqueValue())); } else if (attr.getValues() != null) { @@ -927,7 +910,8 @@ public IntValues getIntValues( } case DERIVED -> { - Optional.ofNullable(derAttrHandler.getValue(realm, (DerSchema) intAttrName.getSchema())). + Optional.ofNullable(derAttrHandler.getValue( + realm, (DerSchema) intAttrName.getSchemaInfo().schema())). ifPresent(derValue -> { PlainAttrValue attrValue = new PlainAttrValue(); attrValue.setStringValue(derValue); @@ -1121,24 +1105,25 @@ public void setIntValues(final Item item, final Attribute attr, final AnyTO anyT default -> { } } - } else if (intAttrName.getSchemaType() != null && attr != null) { + } else if (intAttrName.getSchemaInfo() != null && attr != null) { GroupableRelatableTO groupableTO; Group group; if (anyTO instanceof final GroupableRelatableTO groupableRelatableTO - && intAttrName.getMembershipOfGroup() != null) { + && intAttrName.getMembership() != null) { + groupableTO = groupableRelatableTO; - group = groupDAO.findByName(intAttrName.getMembershipOfGroup()).orElse(null); + group = groupDAO.findByName(intAttrName.getMembership()).orElse(null); } else { groupableTO = null; group = null; } - switch (intAttrName.getSchemaType()) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { Attr attrTO = new Attr(); - attrTO.setSchema(intAttrName.getSchema().getKey()); + attrTO.setSchema(intAttrName.getSchemaInfo().schema().getKey()); - PlainSchema schema = (PlainSchema) intAttrName.getSchema(); + PlainSchema schema = (PlainSchema) intAttrName.getSchemaInfo().schema(); for (Object value : values) { AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType(); @@ -1165,7 +1150,7 @@ public void setIntValues(final Item item, final Attribute attr, final AnyTO anyT case DERIVED -> { Attr attrTO = new Attr(); - attrTO.setSchema(intAttrName.getSchema().getKey()); + attrTO.setSchema(intAttrName.getSchemaInfo().schema().getKey()); if (groupableTO == null || group == null) { anyTO.getDerAttrs().add(attrTO); } else { @@ -1221,13 +1206,13 @@ public void setIntValues(final Item item, final Attribute attr, final RealmTO re default -> { } } - } else if (intAttrName.getSchemaType() != null && attr != null) { - switch (intAttrName.getSchemaType()) { + } else if (intAttrName.getSchemaInfo() != null && attr != null) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { Attr attrTO = new Attr(); - attrTO.setSchema(intAttrName.getSchema().getKey()); + attrTO.setSchema(intAttrName.getSchemaInfo().schema().getKey()); - PlainSchema schema = (PlainSchema) intAttrName.getSchema(); + PlainSchema schema = (PlainSchema) intAttrName.getSchemaInfo().schema(); for (Object value : values) { AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType(); @@ -1245,7 +1230,7 @@ public void setIntValues(final Item item, final Attribute attr, final RealmTO re case DERIVED -> { Attr attrTO = new Attr(); - attrTO.setSchema(intAttrName.getSchema().getKey()); + attrTO.setSchema(intAttrName.getSchemaInfo().schema().getKey()); realmTO.getDerAttrs().add(attrTO); } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java index 6044b3f2eab..ca9ad0bc51b 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java @@ -398,7 +398,6 @@ public DerAttrHandler derAttrHandler(final AnyUtilsFactory anyUtilsFactory, fina @ConditionalOnMissingBean @Bean public MappingManager mappingManager( - final AnyTypeDAO anyTypeDAO, final UserDAO userDAO, final AnyObjectDAO anyObjectDAO, final GroupDAO groupDAO, @@ -411,7 +410,6 @@ public MappingManager mappingManager( final JexlTools jexlTools) { return new DefaultMappingManager( - anyTypeDAO, userDAO, anyObjectDAO, groupDAO, 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 ccb18c197e1..305e23983f2 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 @@ -272,9 +272,9 @@ protected List evaluateMandatoryCondition( } catch (ParseException e) { LOG.error("Invalid intAttrName '{}', ignoring", item.getIntAttrName(), e); } - if (intAttrName != null && intAttrName.getSchema() != null) { - AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema - ? intAttrName.getSchema().getType() + if (intAttrName != null && intAttrName.getSchemaInfo() != null) { + AttrSchemaType schemaType = intAttrName.getSchemaInfo().schema() instanceof PlainSchema + ? intAttrName.getSchemaInfo().schema().getType() : AttrSchemaType.String; MappingManager.IntValues intValues = mappingManager.getIntValues( diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java index ab9572e14c9..248d2a18e1a 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java @@ -231,9 +231,9 @@ protected List evaluateMandatoryCondition(final ExternalResource resourc } catch (ParseException e) { LOG.error("Invalid intAttrName '{}', ignoring", item.getIntAttrName(), e); } - if (intAttrName != null && intAttrName.getSchema() != null) { - AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema - ? intAttrName.getSchema().getType() + if (intAttrName != null && intAttrName.getSchemaInfo() != null) { + AttrSchemaType schemaType = intAttrName.getSchemaInfo().schema() instanceof PlainSchema + ? intAttrName.getSchemaInfo().schema().getType() : AttrSchemaType.String; MappingManager.IntValues intValues = mappingManager.getIntValues( diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java index 9bc5f3ace2d..a954aadafa7 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java @@ -383,24 +383,28 @@ protected void populateMapping( } if (intAttrName == null - || intAttrName.getSchemaType() == null && intAttrName.getField() == null) { + || intAttrName.getSchemaInfo() == null && intAttrName.getField() == null) { LOG.error("'{}' not existing", itemTO.getIntAttrName()); invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not existing"); } else { boolean allowed = true; - if (intAttrName.getSchemaType() != null - && intAttrName.getEnclosingGroup() == null - && intAttrName.getRelatedAnyObject() == null - && intAttrName.getRelationshipType() == null) { - - switch (intAttrName.getSchemaType()) { + if (intAttrName.getSchemaInfo() != null + && intAttrName.getExternalUser() == null + && intAttrName.getExternalGroup() == null + && intAttrName.getExternalAnyObject() == null + && intAttrName.getMembership() != null + && intAttrName.getRelationshipInfo() != null) { + + switch (intAttrName.getSchemaInfo().type()) { case PLAIN: - allowed = allowedSchemas.getPlainSchemas().contains(intAttrName.getSchema().getKey()); + allowed = allowedSchemas.getPlainSchemas(). + contains(intAttrName.getSchemaInfo().schema().getKey()); break; case DERIVED: - allowed = allowedSchemas.getDerSchemas().contains(intAttrName.getSchema().getKey()); + allowed = allowedSchemas.getDerSchemas(). + contains(intAttrName.getSchemaInfo().schema().getKey()); break; default: @@ -446,35 +450,35 @@ protected void populateMapping( mapping.add(item); } - if (intAttrName.getEnclosingGroup() != null + if (intAttrName.getExternalGroup() != null && item.getPurpose() != MappingPurpose.PROPAGATION) { invalidMapping.getElements().add( "Only " + MappingPurpose.PROPAGATION.name() + " allowed when referring to groups"); } - if (intAttrName.getRelatedAnyObject() != null + if (intAttrName.getExternalAnyObject() != null && item.getPurpose() != MappingPurpose.PROPAGATION) { invalidMapping.getElements().add( "Only " + MappingPurpose.PROPAGATION.name() + " allowed when referring to any objects"); } - if (intAttrName.getSchemaType() == SchemaType.DERIVED + if (intAttrName.getSchemaInfo() != null + && intAttrName.getSchemaInfo().type() == SchemaType.DERIVED && item.getPurpose() != MappingPurpose.PROPAGATION) { invalidMapping.getElements().add( "Only " + MappingPurpose.PROPAGATION.name() + " allowed for derived"); } - if (intAttrName.getRelatedUser() != null + if (intAttrName.getExternalUser() != null && item.getPurpose() != MappingPurpose.PROPAGATION) { invalidMapping.getElements().add( "Only " + MappingPurpose.PROPAGATION.name() + " allowed when referring to users"); } - if ((intAttrName.getRelationshipType() != null - || intAttrName.getRelationshipAnyType() != null) + if (intAttrName.getRelationshipInfo() != null && item.getPurpose() != MappingPurpose.PROPAGATION) { invalidMapping.getElements().add( diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java index 5838c75cae6..8553f3b01d1 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java @@ -26,10 +26,11 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; import org.apache.commons.jexl3.JexlContext; import org.apache.commons.jexl3.MapContext; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.common.keymaster.client.api.ConfParamOps; import org.apache.syncope.common.lib.SyncopeConstants; @@ -79,7 +80,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; @Transactional(rollbackFor = { Throwable.class }) public class DefaultNotificationManager implements NotificationManager { @@ -382,25 +382,27 @@ protected String getRecipientEmail(final String recipientAttrName, final User us if ("username".equals(intAttrName.getField())) { email = user.getUsername(); - } else if (intAttrName.getSchemaType() != null) { - UMembership membership = Optional.ofNullable(intAttrName.getMembershipOfGroup()). + } else if (intAttrName.getSchemaInfo() != null) { + UMembership membership = Optional.ofNullable(intAttrName.getMembership()). flatMap(groupDAO::findByName). flatMap(group -> user.getMembership(group.getKey())). orElse(null); - URelationship relationship = Optional.ofNullable(intAttrName.getRelationshipType()). - flatMap(relationshipTypeDAO::findById). - map(user::getRelationships). - filter(Predicate.not(CollectionUtils::isEmpty)). - map(List::getFirst). - orElse(null); - switch (intAttrName.getSchemaType()) { + Mutable relationship = new MutableObject<>(); + Optional.ofNullable(intAttrName.getRelationshipInfo()). + ifPresent(ri -> relationshipTypeDAO.findById(ri.type()). + ifPresent(relationshipType -> anyObjectDAO.findByName( + relationshipType.getRightEndAnyType().getKey(), ri.anyObject()). + ifPresent(otherEnd -> user.getRelationship(relationshipType, otherEnd.getKey()). + ifPresent(relationship::setValue)))); + + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { - Optional attr = membership == null && relationship == null + Optional attr = membership == null && relationship.get() == null ? user.getPlainAttr(recipientAttrName) - : relationship == null - ? user.getPlainAttr(recipientAttrName, membership) - : user.getPlainAttr(recipientAttrName, relationship); + : relationship.get() == null + ? user.getPlainAttr(recipientAttrName, membership) + : user.getPlainAttr(recipientAttrName, relationship.get()); email = attr.map(a -> a.getValuesAsStrings().isEmpty() ? null : a.getValuesAsStrings().getFirst()). @@ -409,11 +411,11 @@ protected String getRecipientEmail(final String recipientAttrName, final User us case DERIVED -> { email = derSchemaDAO.findById(recipientAttrName). - map(derSchema -> membership == null && relationship == null + map(derSchema -> membership == null && relationship.get() == null ? derAttrHandler.getValue(user, derSchema) - : relationship == null - ? derAttrHandler.getValue(user, membership, derSchema) - : derAttrHandler.getValue(user, relationship, derSchema)). + : relationship.get() == null + ? derAttrHandler.getValue(user, membership, derSchema) + : derAttrHandler.getValue(user, relationship.get(), derSchema)). orElse(null); } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java index 6970737e007..31bb5d641a6 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java @@ -294,18 +294,18 @@ public List matchByConnObjectKeyValue( default -> { } } - } else if (intAttrName.getSchemaType() != null) { - switch (intAttrName.getSchemaType()) { + } else if (intAttrName.getSchemaInfo() != null) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN -> { AttrCond attrCond = new AttrCond(ignoreCaseMatch ? AttrCond.Type.IEQ : AttrCond.Type.EQ); - attrCond.setSchema(intAttrName.getSchema().getKey()); + attrCond.setSchema(intAttrName.getSchemaInfo().schema().getKey()); attrCond.setExpression(finalConnObjectKeyValue); anys.addAll(anySearchDAO.search(SearchCond.of(attrCond), anyTypeKind)); } case DERIVED -> anys.addAll(anyUtils.dao().findByDerAttrValue( - ((DerSchema) intAttrName.getSchema()).getExpression(), + ((DerSchema) intAttrName.getSchemaInfo().schema()).getExpression(), finalConnObjectKeyValue, ignoreCaseMatch)); diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java index 48a8a29a5aa..33796a0a6a0 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java @@ -170,21 +170,21 @@ public void fill(final OIDCC4UIProvider op, final OIDCLoginResponse loginRespons default -> LOG.warn("Unsupported: {}", intAttrName.getField()); } - } else if (intAttrName != null && intAttrName.getSchemaType() != null) { - switch (intAttrName.getSchemaType()) { + } else if (intAttrName != null && intAttrName.getSchemaInfo() != null) { + switch (intAttrName.getSchemaInfo().type()) { case PLAIN: - Optional attr = userTO.getPlainAttr(intAttrName.getSchema().getKey()); + Optional attr = userTO.getPlainAttr(intAttrName.getSchemaInfo().schema().getKey()); if (attr.isPresent()) { attr.get().getValues().clear(); } else { - attr = Optional.of(new Attr.Builder(intAttrName.getSchema().getKey()).build()); + attr = Optional.of(new Attr.Builder(intAttrName.getSchemaInfo().schema().getKey()).build()); userTO.getPlainAttrs().add(attr.get()); } attr.get().getValues().addAll(values); break; default: - LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchema().getKey()); + LOG.warn("Unsupported: {}", intAttrName.getSchemaInfo()); } } }); diff --git a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java index 76f09062bae..7b2401d84fa 100644 --- a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java +++ b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java @@ -101,7 +101,7 @@ protected void populateItems(final OIDCC4UIProviderTO opTO, final OIDCC4UIProvid LOG.error("Invalid intAttrName '{}' specified, ignoring", itemTO.getIntAttrName(), e); } - if (intAttrName == null || intAttrName.getSchemaType() == null && intAttrName.getField() == null) { + if (intAttrName == null || intAttrName.getSchemaInfo() == null && intAttrName.getField() == null) { LOG.error("'{}' not existing", itemTO.getIntAttrName()); invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not existing"); } else { diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java index 8459b88ad07..581670dbf9c 100644 --- a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java +++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java @@ -186,21 +186,21 @@ public void fill(final String idpKey, final SAML2LoginResponse loginResponse, fi default -> LOG.warn("Unsupported: {}", intAttrName.getField()); } - } else if (intAttrName != null && intAttrName.getSchemaType() != null) { - switch (intAttrName.getSchemaType()) { - case PLAIN: - Optional attr = userTO.getPlainAttr(intAttrName.getSchema().getKey()); + } else if (intAttrName != null && intAttrName.getSchemaInfo() != null) { + switch (intAttrName.getSchemaInfo().type()) { + case PLAIN -> { + Optional attr = userTO.getPlainAttr(intAttrName.getSchemaInfo().schema().getKey()); if (attr.isPresent()) { attr.get().getValues().clear(); } else { - attr = Optional.of(new Attr.Builder(intAttrName.getSchema().getKey()).build()); + attr = Optional.of(new Attr.Builder(intAttrName.getSchemaInfo().schema().getKey()).build()); userTO.getPlainAttrs().add(attr.get()); } attr.get().getValues().addAll(values); - break; + } - default: - LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchema().getKey()); + default -> + LOG.warn("Unsupported: {}", intAttrName.getSchemaInfo()); } } }); diff --git a/ext/saml2sp4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2SP4UIIdPDataBinderImpl.java b/ext/saml2sp4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2SP4UIIdPDataBinderImpl.java index 5bf8c4d66f6..00fc46ad549 100644 --- a/ext/saml2sp4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2SP4UIIdPDataBinderImpl.java +++ b/ext/saml2sp4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2SP4UIIdPDataBinderImpl.java @@ -102,7 +102,7 @@ protected void populateItems(final SAML2SP4UIIdPTO idpTO, final SAML2SP4UIIdP id LOG.error("Invalid intAttrName '{}' specified, ignoring", itemTO.getIntAttrName(), e); } - if (intAttrName == null || intAttrName.getSchemaType() == null && intAttrName.getField() == null) { + if (intAttrName == null || intAttrName.getSchemaInfo() == null && intAttrName.getField() == null) { LOG.error("'{}' not existing", itemTO.getIntAttrName()); invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not existing"); } else { diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java index 8fd15f5a363..c8e251048f8 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java @@ -74,7 +74,6 @@ import org.apache.syncope.common.lib.to.Provision; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.ReconStatus; -import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.to.TaskTO; import org.apache.syncope.common.lib.to.UserTO; @@ -857,7 +856,7 @@ public void issueSYNCOPE1567() { Item relationships = new Item(); relationships.setPurpose(MappingPurpose.PROPAGATION); - relationships.setIntAttrName("relationships[neighborhood][PRINTER].model"); + relationships.setIntAttrName("anyObjects[HP LJ 1300n].model"); relationships.setExtAttrName("l"); provision.getMapping().add(relationships); @@ -865,16 +864,13 @@ public void issueSYNCOPE1567() { ldap.getProvisions().add(provision); RESOURCE_SERVICE.create(ldap); - // 1. create user with relationship and the new resource assigned + // 1. create user with the new resource assigned UserCR userCR = UserITCase.getUniqueSample("syncope1567@syncope.apache.org"); - userCR.getRelationships().add(new RelationshipTO.Builder("neighborhood"). - otherEnd(PRINTER, "fc6dbc3a-6c07-4965-8781-921e7401a4a5").build()); userCR.getResources().clear(); userCR.getResources().add(ldap.getKey()); UserTO userTO = createUser(userCR).getEntity(); assertNotNull(userTO); - assertFalse(userTO.getRelationships().isEmpty()); // 2. check attributes prepared for propagation PagedResult tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION). @@ -890,8 +886,8 @@ public void issueSYNCOPE1567() { assertEquals("Canon MFC8030", value.getFirst().toString()); // 3. check propagated value - ConnObject connObject = - RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey()); + ConnObject connObject = RESOURCE_SERVICE.readConnObject( + ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey()); assertNotNull(connObject); List values = connObject.getAttr("l").map(Attr::getValues).orElseThrow(); assertFalse(values.isEmpty()); diff --git a/src/main/asciidoc/reference-guide/concepts/externalresources.adoc b/src/main/asciidoc/reference-guide/concepts/externalresources.adoc index 8c629a08405..ee375963b80 100644 --- a/src/main/asciidoc/reference-guide/concepts/externalresources.adoc +++ b/src/main/asciidoc/reference-guide/concepts/externalresources.adoc @@ -125,16 +125,17 @@ specifies: * internal attribute - the <> acting as the source or destination of provisioning operations; it must be specified by an expression matching one of the following models: ** `schema` - resolves to the attribute for the given `schema`, owned by the mapped entity (user, group, any object) -** `groups[groupName].schema` - resolves to the attribute for the given `schema`, owned by the group with name -`groupName`, if a membership for the mapped entity exists -** `users[userName].schema` - resolves to the attribute for the given `schema`, owned by the user with name -`userName`, if a relationship with the mapped entity exists -** `anyObjects[anyObjectName].schema` - resolves to the attribute for the given `schema`, owned by the any object with -name `anyObjectName`, if a relationship with the mapped entity exists -** `relationships[relationshipType][relationshipAnyType].schema` - resolves to the attribute for the given `schema`, -owned by the any object of type `relationshipAnyType`, if a relationship of type `relationshipType` with the mapped entity exists -** `memberships[groupName].schema` - resolves to the attribute for the given `schema`, owned by the membership for group -`groupName` of the mapped entity (user, any object), if such a membership exists +** `groups[name].schema` - resolves to the attribute for the given `schema`, owned by the group with name +`name` +** `users[username].schema` - resolves to the attribute for the given `schema`, owned by the user with username +`username` +** `anyObjects[name].schema` - resolves to the attribute for the given `schema`, owned by the any object with +name `name` +** `memberships[name].schema` - resolves to the attribute for the given `schema`, owned by the membership, of group +with name `name`, of the mapped entity (user, any object), if such a membership exists +** `relationships[type][name].schema` - resolves to the attribute for the given `schema`, owned by the relationship, of +type `type` with any object with name `name`, of the mapped entity (user, group, any object), if such a relationship +exists * external attribute - the name of the attribute on the Identity Store * transformers - <> expression or Java class implementing ifeval::["{snapshotOrRelease}" == "release"] From 697ed41324939d576ca07d6f0702e5f19f39bcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 18 Dec 2025 11:28:05 +0100 Subject: [PATCH 5/7] Console and Enduser --- .../panels/ResourceDirectoryPanel.properties | 2 +- .../ResourceDirectoryPanel_fr_CA.properties | 6 +- .../ResourceDirectoryPanel_it.properties | 2 +- .../ResourceDirectoryPanel_pt_BR.properties | 2 +- .../topology/TopologyTogglePanel.properties | 2 +- .../TopologyTogglePanel_fr_CA.properties | 2 +- .../TopologyTogglePanel_it.properties | 2 +- .../TopologyTogglePanel_pt_BR.properties | 2 +- .../NotificationWizardBuilder.java | 6 +- .../console/panels/GroupDirectoryPanel.java | 25 +++- .../panels/RelationshipTypesPanel.java | 94 ++++++++------ .../panels/TypeExtensionDirectoryPanel.java | 64 ++-------- .../console/wizards/any/AbstractAttrs.java | 117 ++++++++++++----- .../console/wizards/any/AnyWizardBuilder.java | 24 +++- .../client/console/wizards/any/DerAttrs.java | 30 +++++ .../console/wizards/any/PlainAttrs.java | 76 ++++++++++- .../any/TypeExtensionWizardBuilder.java | 21 ++-- .../wizards/mapping/AbstractMappingPanel.java | 6 +- .../console/panels/DirectoryPanel.properties | 2 - .../panels/DirectoryPanel_fr_CA.properties | 1 - .../panels/DirectoryPanel_it.properties | 1 - .../panels/DirectoryPanel_ja.properties | 2 - .../panels/DirectoryPanel_pt_BR.properties | 1 - .../panels/DirectoryPanel_ru.properties | 1 - .../panels/GroupDirectoryPanel.properties | 1 + .../GroupDirectoryPanel_fr_CA.properties | 1 + .../panels/GroupDirectoryPanel_it.properties | 1 + .../panels/GroupDirectoryPanel_ja.properties | 1 + .../GroupDirectoryPanel_pt_BR.properties | 1 + .../panels/GroupDirectoryPanel_ru.properties | 1 + .../panels/RelationshipTypesPanel.properties | 1 + .../RelationshipTypesPanel_fr_CA.properties | 1 + .../RelationshipTypesPanel_it.properties | 1 + .../RelationshipTypesPanel_ja.properties | 1 + .../RelationshipTypesPanel_pt_BR.properties | 1 + .../RelationshipTypesPanel_ru.properties | 1 + .../wizards/any/AbstractAttrs.properties | 1 + .../any/AbstractAttrs_fr_CA.properties | 1 + .../wizards/any/AbstractAttrs_it.properties | 1 + .../wizards/any/AbstractAttrs_ja.properties | 1 + .../any/AbstractAttrs_pt_BR.properties | 1 + .../wizards/any/AbstractAttrs_ru.properties | 1 + .../console/wizards/any/PlainAttrs.html | 5 +- .../enduser/panels/any/AbstractAttrs.java | 70 +++++++++-- .../client/enduser/panels/any/DerAttrs.java | 27 +++- .../client/enduser/panels/any/PlainAttrs.java | 59 ++++++++- .../enduser/rest/SyncopeRestClient.java | 13 +- .../panels/any/AbstractAttrs.properties | 1 + .../panels/any/AbstractAttrs_it.properties | 2 + .../panels/any/AbstractAttrs_ja.properties | 1 + .../panels/any/AbstractAttrs_pt_BR.properties | 1 + .../panels/any/AbstractAttrs_ru.properties | 1 + .../client/enduser/panels/any/PlainAttrs.html | 7 +- .../syncope/common/lib/AnyOperations.java | 98 +++++++-------- .../common/lib/request/RelationshipUR.java | 17 +-- .../apache/syncope/common/lib/to/GroupTO.java | 4 +- .../syncope/common/lib/to/RelationshipTO.java | 5 + .../common/lib/to/RelationshipTypeTO.java | 4 +- .../common/lib/to/TypeExtensionHolderTO.java | 30 +++++ .../rest/api/service/SyncopeService.java | 15 ++- .../core/logic/IdRepoLogicContext.java | 4 + .../syncope/core/logic/SyncopeLogic.java | 28 ++++- .../rest/cxf/service/SyncopeServiceImpl.java | 9 +- .../jpa/entity/anyobject/JPAAnyObject.java | 3 +- .../jpa/entity/group/JPAGroup.java | 9 +- .../persistence/jpa/entity/user/JPAUser.java | 3 +- .../api/data/RelationshipTypeDataBinder.java | 4 + .../provisioning/java/data/AnyDataBinder.java | 118 +++++++----------- .../data/RelationshipTypeDataBinderImpl.java | 3 +- .../java/data/UserDataBinderTest.java | 2 +- .../flowable/CreateARelationship.java | 3 +- .../syncope/fit/console/PoliciesITCase.java | 24 ++-- .../fit/console/RelationshipTypesITCase.java | 4 +- .../syncope/fit/console/UsersITCase.java | 23 +++- .../syncope/fit/core/AnyObjectITCase.java | 16 --- .../syncope/fit/core/RelationshipITCase.java | 32 ++++- pom.xml | 5 + 77 files changed, 774 insertions(+), 385 deletions(-) create mode 100644 common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/TypeExtensionHolderTO.java diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties index 85c330fe1d9..c0e32fc8bd4 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties @@ -22,7 +22,7 @@ resource.edit=Edit resource {0} resource.menu.add=Add new resource resource.menu.remove=Remove resource resource.menu.edit=Edit resource -resource.menu.provision=Edit provision rules +resource.menu.provision=Provision rules resource.menu.explore=Explore resource resource.menu.history=Configuration history resource.menu.clone=Clone resource diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties index ab162bba115..0f05abbd443 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties @@ -22,17 +22,17 @@ resource.edit=Modifier la ressource {0} resource.menu.add=Ajouter une nouvelle ressource resource.menu.remove=Supprimer la ressource resource.menu.edit=Modifier la ressource -resource.menu.provision=Modifier les r\u00e8gles de mise \u00e0 disposition +resource.menu.provision=R\u00e8gles de mise \u00e0 disposition resource.menu.explore=Explorer la ressource resource.menu.history=Historique des configurations resource.menu.clone=Cloner la ressource task.propagation.list=T\u00e2ches de propagation {0} -task.pull.list=T\u00e2ches d\u0027extraction {0} +task.pull.list=T\u00e2ches d'extraction {0} task.push.list=Pousser les t\u00e2ches {0} resource.explore.list=Explorer ${key} resource.reconciliation=R\u00e9conciliation {0} resource.menu.reconciliation=R\u00e9conciliation resource.menu.push.list=Pousser les t\u00e2ches -resource.menu.pull.list=T\u00e2ches d\u0027extraction +resource.menu.pull.list=T\u00e2ches d'extraction resource.menu.propagation.list=T\u00e2ches de propagation diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties index 860655d89f5..b2d92e0dc3b 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties @@ -22,7 +22,7 @@ resource.edit=Modifica risorsa {0} resource.menu.add=Aggiungi nuova risorsa resource.menu.remove=Rimuovi risorsa resource.menu.edit=Modifica risorsa -resource.menu.provision=Modifica regole di provisioning +resource.menu.provision=Regole di provisioning resource.menu.explore=Esplora risorsa resource.menu.history=Storico delle configurazioni resource.menu.clone=Duplica risorsa diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties index 1600a2c4376..1175e6024c6 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties @@ -22,7 +22,7 @@ resource.edit=Editar recurso {0} resource.menu.add=Adicionar novo recurso resource.menu.remove=Remover recurso resource.menu.edit=Editar recurso -resource.menu.provision=Editar regras de provis\u00e3o +resource.menu.provision=Regras de provis\u00e3o resource.menu.explore=Explorar recurso resource.menu.history=Hist\u00f3rico de Configura\u00e7\u00e3o resource.menu.clone=Clonar recurso diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel.properties index c6d63eec6e7..82cf506d607 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel.properties @@ -27,7 +27,7 @@ resource.clone=Clone resource {0} resource.menu.add=Add new resource resource.menu.remove=Remove resource resource.menu.edit=Edit resource -resource.menu.provision=Edit provision rules +resource.menu.provision=Provision rules resource.menu.explore=Explore resource resource.menu.history=Configuration history resource.menu.clone=Clone resource diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_fr_CA.properties index cffa797da47..c3d0a5fe589 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_fr_CA.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_fr_CA.properties @@ -27,7 +27,7 @@ resource.clone=Cloner la ressource {0} resource.menu.add=Ajouter une nouvelle ressource resource.menu.remove=Supprimer une ressource resource.menu.edit=Modifier la ressource -resource.menu.provision=Modifier les r\u00e8gles de provision +resource.menu.provision=R\u00e8gles de provision resource.menu.explore=Explorer la ressource resource.menu.history=Historique de configuration resource.menu.clone=Cloner la ressource diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_it.properties index 8126fd268f8..6e7d8d010be 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_it.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_it.properties @@ -27,7 +27,7 @@ resource.clone=Duplica risorsa {0} resource.menu.add=Aggiungi nuova risorsa resource.menu.remove=Rimuovi risorsa resource.menu.edit=Modifica risorsa -resource.menu.provision=Modifica regole di provisioning +resource.menu.provision=Regole di provisioning resource.menu.explore=Esplora risorsa resource.menu.history=Storico delle configurazioni resource.menu.clone=Duplica risorsa diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_pt_BR.properties index 2c39dc5d858..04125188afd 100644 --- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_pt_BR.properties +++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TopologyTogglePanel_pt_BR.properties @@ -27,7 +27,7 @@ resource.clone=Clone recurso {0} resource.menu.add=Adicionar novo recurso resource.menu.remove=Retire recurso resource.menu.edit=Alterar recurso -resource.menu.provision=Alterar regras de provision +resource.menu.provision=Regras de provision resource.menu.explore=Explorar recurso resource.menu.history=Hist\u00f3rico de configura\u00e7\u00e3o resource.menu.clone=Clone recurso diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java index b58288c48eb..8ea9290e2f5 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java @@ -363,11 +363,11 @@ public Recipients(final NotificationWrapper modelObject) { recipientAttrName.setChoices(getSchemas()); recipientAttrName.addRequiredLabel(); recipientAttrName.setTitle(getString("intAttrNameInfo.help") - + "groups[groupName].attribute, " + + " groups[groupName].attribute, " + "users[userName].attribute, " + "anyObjects[anyObjectName].attribute, " - + "relationships[relationshipType][anyType].attribute or " - + "memberships[groupName].attribute", true); + + "memberships[groupName].attribute, " + + "relationships[relationshipType][anyObjectName].attribute", true); add(recipientAttrName); AjaxTextFieldPanel staticRecipientsFieldPanel = diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java index 79afc603d77..c8b2acbb8f8 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java @@ -260,7 +260,30 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { target.add(typeExtensionsModal.setContent(new TypeExtensionDirectoryPanel( - typeExtensionsModal, model.getObject(), pageRef))); + typeExtensionsModal, model.getObject(), pageRef) { + + private static final long serialVersionUID = -2603789363348077538L; + + @Override + public void onSubmit(final AjaxRequestTarget target) { + GroupUR req = new GroupUR(); + req.setKey(model.getObject().getKey()); + req.getTypeExtensions().addAll(typeExtensionHolder.getTypeExtensions()); + + try { + groupRestClient.update(model.getObject().getETagValue(), req); + + baseModal.show(false); + baseModal.close(target); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("Group update failure", e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); typeExtensionsModal.header(new StringResourceModel("typeExtensions", model)); typeExtensionsModal.show(true); } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java index 023c09b139f..00058b6aae7 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java @@ -18,13 +18,12 @@ */ package org.apache.syncope.client.console.panels; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -33,8 +32,9 @@ import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.RelationshipTypesPanel.RelationshipTypeProvider; import org.apache.syncope.client.console.rest.RelationshipTypeRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.ui.commons.Constants; import org.apache.syncope.client.ui.commons.panels.WizardModalPanel; @@ -51,12 +51,15 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; public class RelationshipTypesPanel extends TypesDirectoryPanel< RelationshipTypeTO, RelationshipTypeProvider, RelationshipTypeRestClient> { private static final long serialVersionUID = -3731778000138547357L; + protected final BaseModal typeExtensionsModal = new BaseModal<>(Constants.OUTER); + public RelationshipTypesPanel( final String id, final RelationshipTypeRestClient restClient, @@ -65,6 +68,11 @@ public RelationshipTypesPanel( super(id, restClient, false, pageRef); disableCheckBoxes(); + typeExtensionsModal.size(Modal.Size.Large); + typeExtensionsModal.addSubmitButton(); + setWindowClosedReloadCallback(typeExtensionsModal); + addOuterObject(typeExtensionsModal); + this.addNewItemPanelBuilder( new AbstractModalPanelBuilder(new RelationshipTypeTO(), pageRef) { @@ -121,41 +129,18 @@ protected Collection getBatches() { @Override protected List> getColumns() { - - final List> columns = new ArrayList<>(); - - for (Field field : RelationshipTypeTO.class.getDeclaredFields()) { - if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) { - final String fieldName = field.getName(); - if (field.getType().isArray() - || Collection.class.isAssignableFrom(field.getType()) - || Map.class.isAssignableFrom(field.getType())) { - - columns.add(new PropertyColumn<>( - new ResourceModel(field.getName()), field.getName())); - } else if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) { - columns.add(new BooleanPropertyColumn<>( - new ResourceModel(field.getName()), field.getName(), field.getName())); - } else { - columns.add(new PropertyColumn<>( - new ResourceModel(field.getName()), field.getName(), field.getName()) { - - private static final long serialVersionUID = -6902459669035442212L; - - @Override - public String getCssClass() { - String css = super.getCssClass(); - if (Constants.KEY_FIELD_NAME.equals(fieldName)) { - css = StringUtils.isBlank(css) - ? "col-xs-1" - : css + " col-xs-1"; - } - return css; - } - }); - } - } - } + List> columns = new ArrayList<>(); + + columns.add(new PropertyColumn<>( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); + columns.add(new PropertyColumn<>( + new ResourceModel(Constants.DESCRIPTION_FIELD_NAME), + Constants.DESCRIPTION_FIELD_NAME, + Constants.DESCRIPTION_FIELD_NAME)); + columns.add(new PropertyColumn<>( + new ResourceModel("leftEndAnyType"), "leftEndAnyType", "leftEndAnyType")); + columns.add(new PropertyColumn<>( + new ResourceModel("rightEndAnyType"), "rightEndAnyType", "rightEndAnyType")); return columns; } @@ -174,6 +159,37 @@ public void onClick(final AjaxRequestTarget target, final RelationshipTypeTO ign new AjaxWizard.EditItemActionEvent<>(model.getObject(), target)); } }, ActionLink.ActionType.EDIT, IdRepoEntitlement.RELATIONSHIPTYPE_UPDATE); + panel.add(new ActionLink<>() { + + private static final long serialVersionUID = 6242834621660352855L; + + @Override + public void onClick(final AjaxRequestTarget target, final RelationshipTypeTO ignore) { + target.add(typeExtensionsModal.setContent(new TypeExtensionDirectoryPanel( + typeExtensionsModal, model.getObject(), pageRef) { + + private static final long serialVersionUID = -2603789363348077538L; + + @Override + public void onSubmit(final AjaxRequestTarget target) { + try { + RelationshipTypesPanel.this.restClient.update(model.getObject()); + + baseModal.show(false); + baseModal.close(target); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("RelationshipType update failure", e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + typeExtensionsModal.header(new StringResourceModel("typeExtensions", model)); + typeExtensionsModal.show(true); + } + }, ActionType.TYPE_EXTENSIONS, IdRepoEntitlement.RELATIONSHIPTYPE_UPDATE); panel.add(new ActionLink<>() { private static final long serialVersionUID = -3722207913631435501L; diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java index 01aeeefe02e..d5c0ceb8e49 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java @@ -32,7 +32,6 @@ import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; import org.apache.syncope.client.console.rest.AnyTypeRestClient; import org.apache.syncope.client.console.rest.BaseRestClient; -import org.apache.syncope.client.console.rest.GroupRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; @@ -40,8 +39,7 @@ import org.apache.syncope.client.ui.commons.Constants; import org.apache.syncope.client.ui.commons.panels.SubmitableModalPanel; import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; -import org.apache.syncope.common.lib.request.GroupUR; -import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.TypeExtensionHolderTO; import org.apache.syncope.common.lib.to.TypeExtensionTO; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -55,15 +53,12 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; -public class TypeExtensionDirectoryPanel +public abstract class TypeExtensionDirectoryPanel extends DirectoryPanel implements SubmitableModalPanel { private static final long serialVersionUID = -4117015319209624858L; - @SpringBean - protected GroupRestClient groupRestClient; - @SpringBean protected AnyTypeRestClient anyTypeRestClient; @@ -72,20 +67,20 @@ public class TypeExtensionDirectoryPanel protected final BaseModal baseModal; - protected final GroupTO groupTO; + protected final TypeExtensionHolderTO typeExtensionHolder; protected TypeExtensionDirectoryPanel( final BaseModal baseModal, - final GroupTO groupTO, + final TypeExtensionHolderTO typeExtensionHolder, final PageReference pageRef) { super(BaseModal.CONTENT_ID, null, pageRef, false); this.baseModal = baseModal; - this.groupTO = groupTO; + this.typeExtensionHolder = typeExtensionHolder; TypeExtensionWizardBuilder builder = new TypeExtensionWizardBuilder( - groupTO, + typeExtensionHolder, new TypeExtensionTO(), new StringResourceModel("anyType", this).getObject(), new StringResourceModel("auxClasses", this).getObject(), @@ -98,26 +93,6 @@ protected TypeExtensionDirectoryPanel( initResultTable(); } - @Override - public void onSubmit(final AjaxRequestTarget target) { - GroupUR req = new GroupUR(); - req.setKey(groupTO.getKey()); - req.getTypeExtensions().addAll(groupTO.getTypeExtensions()); - - try { - groupRestClient.update(groupTO.getETagValue(), req); - - this.baseModal.show(false); - this.baseModal.close(target); - - SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); - } catch (Exception e) { - LOG.error("Group update failure", e); - SyncopeConsoleSession.get().onException(e); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - @Override public void onError(final AjaxRequestTarget target) { SyncopeConsoleSession.get().error(getString(Constants.OPERATION_ERROR)); @@ -167,8 +142,8 @@ public void onClick(final AjaxRequestTarget target, final TypeExtensionTO ignore @Override public void onClick(final AjaxRequestTarget target, final TypeExtensionTO ignore) { - groupTO.getTypeExtension(typeExtension.getAnyType()).ifPresent(typeExt -> { - groupTO.getTypeExtensions().remove(typeExt); + typeExtensionHolder.getTypeExtension(typeExtension.getAnyType()).ifPresent(typeExt -> { + typeExtensionHolder.getTypeExtensions().remove(typeExt); target.add(container); }); } @@ -193,36 +168,17 @@ public TypeExtensionDataProvider(final int paginatorRows) { @Override public Iterator iterator(final long first, final long count) { - return groupTO.getTypeExtensions().subList((int) first, (int) (first + count)).iterator(); + return typeExtensionHolder.getTypeExtensions().subList((int) first, (int) (first + count)).iterator(); } @Override public long size() { - return groupTO.getTypeExtensions().size(); + return typeExtensionHolder.getTypeExtensions().size(); } @Override public IModel model(final TypeExtensionTO object) { return new CompoundPropertyModel<>(object); } - - } - - @Override - protected void customActionCallback(final AjaxRequestTarget target) { - // change modal footer visibility - send(TypeExtensionDirectoryPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); - } - - @Override - protected void customActionOnCancelCallback(final AjaxRequestTarget target) { - // change modal footer visibility - send(TypeExtensionDirectoryPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); - } - - @Override - protected void customActionOnFinishCallback(final AjaxRequestTarget target) { - // change modal footer visibility - send(TypeExtensionDirectoryPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); } } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java index 698514d48e0..ce63c083c60 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java @@ -24,18 +24,22 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.rest.GroupRestClient; +import org.apache.syncope.client.console.rest.RelationshipTypeRestClient; import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper; import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AnyTypeClassTO; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelatableTO; +import org.apache.syncope.common.lib.to.RelationshipTO; +import org.apache.syncope.common.lib.to.RelationshipTypeTO; import org.apache.syncope.common.lib.to.SchemaTO; import org.apache.syncope.common.lib.to.TypeExtensionTO; -import org.apache.wicket.WicketRuntimeException; -import org.apache.wicket.core.util.lang.PropertyResolver; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.OnDomReadyHeaderItem; import org.apache.wicket.model.IModel; @@ -49,10 +53,17 @@ public abstract class AbstractAttrs extends AbstractAttrsWiz @SpringBean protected GroupRestClient groupRestClient; + @SpringBean + protected RelationshipTypeRestClient relationshipTypeRestClient; + protected final IModel> memberships; + protected final IModel> relationships; + protected final Map> membershipSchemas = new LinkedHashMap<>(); + protected final Map, Map> relationshipSchemas = new LinkedHashMap<>(); + public AbstractAttrs( final AnyWrapper modelObject, final AjaxWizard.Mode mode, @@ -62,33 +73,29 @@ public AbstractAttrs( super(modelObject.getInnerObject(), mode, anyTypeClasses, whichAttrs); this.memberships = new ListModel<>(List.of()); + this.relationships = new ListModel<>(List.of()); this.setOutputMarkupId(true); } - @SuppressWarnings("unchecked") private List loadMemberships() { - if (attributable instanceof AnyTO anyTO) { + if (attributable instanceof GroupableRelatableTO anyTO) { membershipSchemas.clear(); List membs = new ArrayList<>(); - try { - ((Iterable) PropertyResolver.getPropertyField("memberships", anyTO).get(anyTO)). - forEach(memb -> { - setSchemas(memb.getGroupKey(), - anyTypeClassRestClient.list(getMembershipAuxClasses(memb, anyTO.getType())). - stream().map(AnyTypeClassTO::getKey).collect(Collectors.toList())); - setAttrs(memb); - - if (this instanceof PlainAttrs && !memb.getPlainAttrs().isEmpty()) { - membs.add(memb); - } else if (this instanceof DerAttrs && !memb.getDerAttrs().isEmpty()) { - membs.add(memb); - } - }); - } catch (WicketRuntimeException | IllegalArgumentException | IllegalAccessException ex) { - // ignore - } + anyTO.getMemberships().forEach(memb -> { + setSchemas(memb.getGroupKey(), + anyTypeClassRestClient.list(getMembershipAuxClasses( + memb.getGroupKey(), ((AnyTO) anyTO).getType())).stream(). + map(AnyTypeClassTO::getKey).collect(Collectors.toList())); + setAttrs(memb); + + if (this instanceof PlainAttrs && !memb.getPlainAttrs().isEmpty()) { + membs.add(memb); + } else if (this instanceof DerAttrs && !memb.getDerAttrs().isEmpty()) { + membs.add(memb); + } + }); return membs; } @@ -96,6 +103,31 @@ private List loadMemberships() { return List.of(); } + private List loadRelationships() { + if (attributable instanceof RelatableTO anyTO) { + relationshipSchemas.clear(); + + List rels = new ArrayList<>(); + anyTO.getRelationships().forEach(rel -> { + setSchemas(Pair.of(rel.getType(), rel.getOtherEndKey()), + anyTypeClassRestClient.list(getRelationshipAuxClasses( + rel.getType(), ((AnyTO) anyTO).getType())).stream(). + map(AnyTypeClassTO::getKey).collect(Collectors.toList())); + setAttrs(rel); + + if (this instanceof PlainAttrs && !rel.getPlainAttrs().isEmpty()) { + rels.add(rel); + } else if (this instanceof DerAttrs && !rel.getDerAttrs().isEmpty()) { + rels.add(rel); + } + }); + + return rels; + } + + return List.of(); + } + private void setSchemas(final String membership, final List anyTypeClasses) { final Map mscs; @@ -108,12 +140,31 @@ private void setSchemas(final String membership, final List anyTypeClass setSchemas(anyTypeClasses, mscs); } - protected List getMembershipAuxClasses(final MembershipTO membershipTO, final String anyType) { + private void setSchemas(final Pair relationship, final List anyTypeClasses) { + final Map mscs; + + if (relationshipSchemas.containsKey(relationship)) { + mscs = relationshipSchemas.get(relationship); + } else { + mscs = new LinkedHashMap<>(); + relationshipSchemas.put(relationship, mscs); + } + setSchemas(anyTypeClasses, mscs); + } + + protected List getMembershipAuxClasses(final String group, final String anyType) { try { - GroupTO groupTO = groupRestClient.read(membershipTO.getGroupKey()); - return groupTO.getTypeExtension(anyType). - map(TypeExtensionTO::getAuxClasses). - orElseGet(List::of); + GroupTO groupTO = groupRestClient.read(group); + return groupTO.getTypeExtension(anyType).map(TypeExtensionTO::getAuxClasses).orElseGet(List::of); + } catch (Exception e) { + return List.of(); + } + } + + protected List getRelationshipAuxClasses(final String relationshipType, final String anyType) { + try { + RelationshipTypeTO typeTO = relationshipTypeRestClient.read(relationshipType); + return typeTO.getTypeExtension(anyType).map(TypeExtensionTO::getAuxClasses).orElseGet(List::of); } catch (Exception e) { return List.of(); } @@ -121,12 +172,19 @@ protected List getMembershipAuxClasses(final MembershipTO membershipTO, protected abstract void setAttrs(MembershipTO membershipTO); + protected abstract void setAttrs(RelationshipTO relationshipTO); + protected abstract List getAttrsFromTO(MembershipTO membershipTO); + protected abstract List getAttrsFromTO(RelationshipTO relationshipTO); + @Override public void renderHead(final IHeaderResponse response) { super.renderHead(response); - if (CollectionUtils.isEmpty(attrs.getObject()) && CollectionUtils.isEmpty(memberships.getObject())) { + if (CollectionUtils.isEmpty(attrs.getObject()) + && CollectionUtils.isEmpty(memberships.getObject()) + && CollectionUtils.isEmpty(relationships.getObject())) { + response.render(OnDomReadyHeaderItem.forScript( String.format("$('#emptyPlaceholder').append(\"%s\"); $('#attributes').hide();", getString("attribute.empty.list")))); @@ -137,6 +195,9 @@ public void renderHead(final IHeaderResponse response) { public boolean evaluate() { this.attrs.setObject(loadAttrs()); this.memberships.setObject(loadMemberships()); - return !attrs.getObject().isEmpty() || !memberships.getObject().isEmpty(); + this.relationships.setObject(loadRelationships()); + return !attrs.getObject().isEmpty() + || !memberships.getObject().isEmpty() + || !relationships.getObject().isEmpty(); } } 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 f62a8870de3..d00eb16db43 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 @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.Future; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.SyncopeWebApplication; @@ -47,6 +48,8 @@ public abstract class AnyWizardBuilder extends AbstractAnyWizar private static final long serialVersionUID = -2480279868319546243L; + private static final List NO_VALUES = List.of(StringUtils.EMPTY); + @SuppressWarnings("unchecked") protected static AnyWrapper wrapper(final T anyTO) { return (AnyWrapper) (anyTO instanceof UserTO userTO @@ -123,6 +126,10 @@ protected WizardModel buildModelSteps(final AnyWrapper modelObject, final Wiz wizardModel.add(new Groups(modelObject, mode == AjaxWizard.Mode.TEMPLATE)); } + if (formLayoutInfo.isRelationships()) { + wizardModel.add(new Relationships(modelObject, pageRef)); + } + // attributes panel steps if (formLayoutInfo.isPlainAttrs()) { wizardModel.add(new PlainAttrs(modelObject, mode, anyTypeClasses, formLayoutInfo.getWhichPlainAttrs()) { @@ -148,10 +155,6 @@ public PageReference getPageReference() { wizardModel.add(new Roles(userWrapper)); } - if (formLayoutInfo.isRelationships()) { - wizardModel.add(new Relationships(modelObject, pageRef)); - } - SyncopeWebApplication.get().getAnyWizardBuilderAdditionalSteps(). buildModelSteps(modelObject, wizardModel, formLayoutInfo); @@ -182,14 +185,23 @@ protected void fixPlainAttrs(final AnyTO updated, final AnyTO original) { ifPresent(uMemb -> oMemb.getPlainAttrs().stream(). filter(attr -> uMemb.getPlainAttr(attr.getSchema()).isEmpty()). forEach(attr -> uMemb.getPlainAttrs().add(attr)))); + originalTO.getRelationships(). + forEach(oRel -> updatedTO.getRelationship(oRel.getType(), oRel.getOtherEndKey()). + ifPresent(uRel -> oRel.getPlainAttrs().stream(). + filter(attr -> uRel.getPlainAttr(attr.getSchema()).isEmpty()). + forEach(attr -> uRel.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()); + updated.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty() || NO_VALUES.equals(attr.getValues())); if (updated instanceof GroupableRelatableTO updatedTO) { updatedTO.getMemberships(). - forEach(memb -> memb.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty())); + forEach(memb -> memb.getPlainAttrs(). + removeIf(attr -> attr.getValues().isEmpty() || NO_VALUES.equals(attr.getValues()))); + updatedTO.getRelationships(). + forEach(rel -> rel.getPlainAttrs(). + removeIf(attr -> attr.getValues().isEmpty() || NO_VALUES.equals(attr.getValues()))); } } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java index 0f8295c8a88..e41ab711d9a 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion; @@ -35,6 +36,7 @@ import org.apache.syncope.common.lib.to.DerSchemaTO; import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.markup.ComponentTag; @@ -113,6 +115,11 @@ protected List getAttrsFromTO(final MembershipTO membershipTO) { return membershipTO.getDerAttrs().stream().sorted(attrComparator).collect(Collectors.toList()); } + @Override + protected List getAttrsFromTO(final RelationshipTO relationshipTO) { + return relationshipTO.getDerAttrs().stream().sorted(attrComparator).collect(Collectors.toList()); + } + @Override protected void setAttrs() { List derAttrs = new ArrayList<>(); @@ -154,6 +161,29 @@ protected void setAttrs(final MembershipTO membershipTO) { membershipTO.getDerAttrs().addAll(derAttrs); } + @Override + protected void setAttrs(final RelationshipTO relationshipTO) { + Map attrMap = GroupableRelatableTO.class.cast(attributable). + getRelationship(relationshipTO.getType(), relationshipTO.getOtherEndKey()). + map(gr -> EntityTOUtils.buildAttrMap(gr.getDerAttrs())). + orElseGet(HashMap::new); + + List derAttrs = relationshipSchemas.get( + Pair.of(relationshipTO.getType(), relationshipTO.getOtherEndKey())).values().stream().map(schema -> { + + Attr attr = new Attr(); + attr.setSchema(schema.getKey()); + if (attrMap.containsKey(schema.getKey())) { + attr.getValues().addAll(attrMap.get(schema.getKey()).getValues()); + } + + return attr; + }).toList(); + + relationshipTO.getDerAttrs().clear(); + relationshipTO.getDerAttrs().addAll(derAttrs); + } + public static class DerSchemas extends Schemas { private static final long serialVersionUID = -4730563859116024676L; diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java index bb000382812..f44a5e02349 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion; import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper; @@ -35,6 +36,7 @@ import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; 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.AttrSchemaType; import org.apache.syncope.common.lib.types.SchemaType; @@ -90,7 +92,7 @@ public WebMarkupContainer getPanel(final String panelId) { @Override protected void populateItem(final ListItem item) { - final MembershipTO membershipTO = item.getModelObject(); + MembershipTO membershipTO = item.getModelObject(); item.add(new Accordion("membershipPlainSchemas", List.of(new AbstractTab( new StringResourceModel( "attributes.membership.accordion", @@ -101,7 +103,7 @@ protected void populateItem(final ListItem item) { @Override public WebMarkupContainer getPanel(final String panelId) { - return new PlainSchemasMemberships( + return new PlainSchemasAttributable( panelId, membershipSchemas.get(membershipTO.getGroupKey()), new LoadableDetachableModel<>() { @@ -117,6 +119,41 @@ protected AttributableTO load() { }), Model.of(-1)).setOutputMarkupId(true)); } }); + + add(new ListView<>("relationshipsPlainSchemas", relationships) { + + private static final long serialVersionUID = 6741044372185745296L; + + @Override + protected void populateItem(final ListItem item) { + RelationshipTO relationshipTO = item.getModelObject(); + item.add(new Accordion("relationshipPlainSchemas", List.of(new AbstractTab( + new StringResourceModel( + "attributes.relationship.accordion", + PlainAttrs.this, + Model.of(relationshipTO))) { + + private static final long serialVersionUID = 1037272333056449378L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new PlainSchemasAttributable( + panelId, + relationshipSchemas.get(Pair.of( + relationshipTO.getType(), relationshipTO.getOtherEndKey())), + new LoadableDetachableModel<>() { + + private static final long serialVersionUID = 526768546610546553L; + + @Override + protected AttributableTO load() { + return relationshipTO; + } + }); + } + }), Model.of(-1)).setOutputMarkupId(true)); + } + }); } @Override @@ -139,6 +176,11 @@ protected List getAttrsFromTO(final MembershipTO membershipTO) { return membershipTO.getPlainAttrs().stream().sorted(attrComparator).collect(Collectors.toList()); } + @Override + protected List getAttrsFromTO(final RelationshipTO relationshipTO) { + return relationshipTO.getPlainAttrs().stream().sorted(attrComparator).collect(Collectors.toList()); + } + @Override protected void setAttrs() { Map attrMap = EntityTOUtils.buildAttrMap(attributable.getPlainAttrs()); @@ -184,6 +226,32 @@ protected void setAttrs(final MembershipTO membershipTO) { membershipTO.getPlainAttrs().addAll(plainAttrs); } + @Override + protected void setAttrs(final RelationshipTO relationshipTO) { + Map attrMap = GroupableRelatableTO.class.cast(attributable). + getRelationship(relationshipTO.getType(), relationshipTO.getOtherEndKey()). + map(gr -> EntityTOUtils.buildAttrMap(gr.getPlainAttrs())). + orElseGet(HashMap::new); + + List plainAttrs = relationshipSchemas.get( + Pair.of(relationshipTO.getType(), relationshipTO.getOtherEndKey())).values().stream().map(schema -> { + + Attr attr = new Attr(); + attr.setSchema(schema.getKey()); + if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { + if (schema.getType() != AttrSchemaType.Dropdown || !schema.isMultivalue()) { + attr.getValues().add(StringUtils.EMPTY); + } + } else { + attr.getValues().addAll(attrMap.get(schema.getKey()).getValues()); + } + return attr; + }).toList(); + + relationshipTO.getPlainAttrs().clear(); + relationshipTO.getPlainAttrs().addAll(plainAttrs); + } + protected class PlainSchemasOwn extends PlainSchemas> { private static final long serialVersionUID = -4730563859116024676L; @@ -208,11 +276,11 @@ protected void populateItem(final ListItem item) { } } - protected class PlainSchemasMemberships extends PlainSchemas { + protected class PlainSchemasAttributable extends PlainSchemas { private static final long serialVersionUID = 456754923340249215L; - public PlainSchemasMemberships( + public PlainSchemasAttributable( final String id, final Map schemas, final IModel attributableTO) { diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java index 223dec574d6..43512a77334 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java @@ -29,6 +29,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.to.AnyTypeClassTO; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.TypeExtensionHolderTO; import org.apache.syncope.common.lib.to.TypeExtensionTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.wicket.PageReference; @@ -42,7 +43,7 @@ public class TypeExtensionWizardBuilder extends BaseAjaxWizardBuilder typeExtensions = groupTO.getTypeExtensions().stream(). + List typeExtensions = typeExtensionHolder.getTypeExtensions().stream(). filter(typeExt -> !typeExt.getAnyType().equals(modelObject.getAnyType())).collect(Collectors.toList()); typeExtensions.add(modelObject); - groupTO.getTypeExtensions().clear(); - groupTO.getTypeExtensions().addAll(typeExtensions); - return groupTO; + typeExtensionHolder.getTypeExtensions().clear(); + typeExtensionHolder.getTypeExtensions().addAll(typeExtensions); + return typeExtensionHolder; } public class Details extends WizardStep { @@ -98,9 +99,11 @@ public Details(final TypeExtensionTO typeExtensionTO) { if (typeExtensionTO.getAnyType() == null) { List anyTypes = anyTypeRestClient.list(); - anyTypes.remove(AnyTypeKind.GROUP.name()); + if (typeExtensionHolder instanceof GroupTO) { + anyTypes.remove(AnyTypeKind.GROUP.name()); + } anyTypes.removeAll(anyTypes.stream(). - filter(anyType -> groupTO.getTypeExtension(anyType).isPresent()).toList()); + filter(anyType -> typeExtensionHolder.getTypeExtension(anyType).isPresent()).toList()); AjaxDropDownChoicePanel anyTypeComponent = new AjaxDropDownChoicePanel<>( "anyType.component", "anyType", new PropertyModel<>(typeExtensionTO, "anyType")); diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/mapping/AbstractMappingPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/mapping/AbstractMappingPanel.java index 5c79d616eef..1b1bc84840f 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/mapping/AbstractMappingPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/mapping/AbstractMappingPanel.java @@ -149,11 +149,11 @@ public AbstractMappingPanel( intAttrNameInfo.add(new PopoverBehavior( Model.of(), Model.of(getString("intAttrNameInfo.help") - + "groups[groupName].attribute, " + + " groups[groupName].attribute, " + "users[userName].attribute, " + "anyObjects[anyObjectName].attribute, " - + "relationships[relationshipType][anyType].attribute or " - + "memberships[groupName].attribute"), + + "memberships[groupName].attribute, " + + "relationships[relationshipType][anyObjectName].attribute"), new PopoverConfig().withHtml(true).withPlacement(TooltipConfig.Placement.right)) { private static final long serialVersionUID = -7867802555691605021L; diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel.properties index 9c394fc9553..44c8e2a4ad1 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel.properties @@ -39,8 +39,6 @@ batch=Batch any.propagation.tasks=Propagation tasks for ${type} ${name} any.notification.tasks=Notification tasks for ${type} ${name} notification.tasks=Tasks about notification ${key} - -typeExtensions=Type extensions for GROUP ${name} creator=Creator creationContext=Creation Context lastChangeContext=Last Change Context diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_fr_CA.properties index 2b04856ffce..c69bd48cf58 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_fr_CA.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_fr_CA.properties @@ -36,7 +36,6 @@ batch=Lot any.propagation.tasks=T\u00e2ches de propagation pour ${type} ${name} any.notification.tasks=T\u00e2ches de notification pour ${type} ${name} notification.tasks=T\u00e2ches \u00e0 propos de la notification ${key} -typeExtensions=Taper les extensions pour GROUPE ${name} creator=Cr\u00e9ateur creationContext=Creation Context lastChangeContext=Last Change Context diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_it.properties index e36a31d674b..118ddc7d7c2 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_it.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_it.properties @@ -39,7 +39,6 @@ batch=Batch any.propagation.tasks=Task di Propagazione per ${type} ${name} any.notification.tasks=Task di Notifica per ${type} ${name} notification.tasks=Task relativi alla notifica ${key} -typeExtensions=Type extensions per GROUP ${name} creator=Creatore creationContext=Contesto Di Crezione lastChangeContext=Contesto Di Ultima Modifica diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ja.properties index a62d3a840f7..29e4faf648c 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ja.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ja.properties @@ -39,8 +39,6 @@ batch=\u30d0\u30c3\u30c1 any.propagation.tasks=${type} ${name} \u306e\u4f1d\u64ad\u30bf\u30b9\u30af any.notification.tasks=${type} ${name} \u306e\u901a\u77e5\u30bf\u30b9\u30af notification.tasks=\u901a\u77e5 ${key} \u306b\u95a2\u3059\u308b\u30bf\u30b9\u30af - -typeExtensions=\u30b0\u30eb\u30fc\u30d7 ${name} \u306e\u30bf\u30a4\u30d7\u62e1\u5f35 creator=\u4f5c\u6210\u8005 creationContext=Creation Context lastChangeContext=Last Change Context diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_pt_BR.properties index bd1d7260a33..b285a4911ba 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_pt_BR.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_pt_BR.properties @@ -39,7 +39,6 @@ batch=Batch any.propagation.tasks=Propagation tasks for ${type} ${name} any.notification.tasks=Notification tasks for ${type} ${name} notification.tasks=Tasks about notification ${key} -typeExtensions=Type extensions for GROUP ${name} creator=Criador creationContext=Creation Context lastChangeContext=Last Change Context diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ru.properties index 26076067753..dd31e40e7fe 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ru.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ru.properties @@ -40,7 +40,6 @@ batch=Batch any.propagation.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f ${type} ${name} any.notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f ${type} ${name} notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 ${key} -typeExtensions=\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0430 \u0434\u043b\u044f \u0413\u0440\u0443\u043f\u043f\u044b ${name} creator=\u0421\u043e\u0437\u0434\u0430\u043b creationContext=Creation Context lastChangeContext=Last Change Context diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties index 7c1cb8cdc53..9b6c4d16a11 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties @@ -17,3 +17,4 @@ any.edit=Edit ${anyTO.type} ${anyTO.name} group.members=${right} members of ${left.name} auditHistory.title=${anyTO.type} ${anyTO.name} history +typeExtensions=Type extensions for GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_fr_CA.properties index f2b1ceff00b..963b8c912f2 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_fr_CA.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_fr_CA.properties @@ -17,3 +17,4 @@ any.edit=Modifier ${anyTO.type} ${anyTO.name} group.members=${right} membres de ${left.name} auditHistory.title=${anyTO.type} ${anyTO.name} histoire +typeExtensions=Type extensions for GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties index 8566276c489..9912ea63529 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties @@ -17,3 +17,4 @@ any.edit=Modifica ${anyTO.type} ${anyTO.name} group.members=Membri ${right} di '${left.name}' auditHistory.title=${anyTO.type} ${anyTO.name} history +typeExtensions=Type extensions per GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties index 2f0982a197f..c91f74dbebd 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties @@ -17,3 +17,4 @@ any.edit=${anyTO.type} ${anyTO.name} \u3092\u7de8\u96c6 group.members=${left.name} \u306e ${right} \u30e1\u30f3\u30d0\u30fc auditHistory.title=${anyTO.type} ${anyTO.name} history +typeExtensions=Type extensions for GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties index b5730e23ccb..f3ee5f48f36 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties @@ -17,3 +17,4 @@ any.edit=Alterar ${anyTO.type} ${anyTO.name} group.members=${right} members of ${left.name} auditHistory.title=${anyTO.type} ${anyTO.name} history +typeExtensions=Type extensions for GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties index 84e3f7b4ecd..4a868f43aed 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties @@ -18,3 +18,4 @@ any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${anyTO.type} ${anyTO.name} group.members=${right} \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 ${left.name} auditHistory.title=${anyTO.type} ${anyTO.name} history +typeExtensions=Type extensions for GROUP ${name} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel.properties index c41e4711bfa..368d855bafd 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel.properties @@ -19,3 +19,4 @@ any.new=New RelationshipType leftEndAnyType=Left End AnyType rightEndAnyType=Right End AnyType description=Description +typeExtensions=Type extensions for RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_fr_CA.properties index 89e67c8b236..88163315ddb 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_fr_CA.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_fr_CA.properties @@ -19,3 +19,4 @@ any.new=Nouveau type de relation leftEndAnyType=Left End AnyType rightEndAnyType=Right End AnyType description=Description +typeExtensions=Type extensions for RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_it.properties index e9ec8689ef7..339684e7b85 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_it.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_it.properties @@ -19,3 +19,4 @@ any.new=Nuovo RelationshipType leftEndAnyType=AnyType lato sinistro rightEndAnyType=AnyType lato destro description=Descrizione +typeExtensions=Type extensions per RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ja.properties index 376289e60f2..6b38da5a1d3 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ja.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ja.properties @@ -19,3 +19,4 @@ any.new=\u65b0\u3057\u3044\u95a2\u4fc2\u30bf\u30a4\u30d7 leftEndAnyType=Left End AnyType rightEndAnyType=Right End AnyType description=Description +typeExtensions=Type extensions for RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_pt_BR.properties index bf5e41f9634..825bb27c712 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_pt_BR.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_pt_BR.properties @@ -19,3 +19,4 @@ any.new=Novo RelationshipType leftEndAnyType=Left End AnyType rightEndAnyType=Right End AnyType description=Description +typeExtensions=Type extensions for RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ru.properties index bf61f42a917..52b2f48b7b8 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ru.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/RelationshipTypesPanel_ru.properties @@ -22,3 +22,4 @@ any.new=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0442\u0438\u043f \u0441\u04 leftEndAnyType=Left End AnyType rightEndAnyType=Right End AnyType description=Description +typeExtensions=Type extensions for RelationshipType ${key} diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs.properties index f6c0c024f65..200b1a020bd 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Own attributes.membership.accordion=From membership with '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_fr_CA.properties index be50d3fb661..82afcfe0513 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_fr_CA.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_fr_CA.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Moi attributes.membership.accordion=De regroupements avec '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_it.properties index 9b9f1a0c0b6..eb6b11bca50 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_it.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_it.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Propri attributes.membership.accordion=Dalla membership con '${groupName}' +attributes.relationship.accordion=Dalla relationship ${type} con '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ja.properties index 63f5ae6aa35..1125a339495 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ja.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ja.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=\u81ea\u5206 attributes.membership.accordion='${groupName}' \u3068\u306e\u30e1\u30f3\u30d0\u30fc\u30b7\u30c3\u30d7\u304b\u3089 +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_pt_BR.properties index f6c0c024f65..200b1a020bd 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_pt_BR.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_pt_BR.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Own attributes.membership.accordion=From membership with '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ru.properties index b28194c117f..e2303dc3c9e 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ru.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/AbstractAttrs_ru.properties @@ -17,3 +17,4 @@ # attributes.accordion=\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 attributes.membership.accordion=\u0418\u0437 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u0433\u0440\u0443\u043f\u043f\u044b '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/PlainAttrs.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/PlainAttrs.html index 8da9bd9e74c..4196fb3c677 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/PlainAttrs.html +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/PlainAttrs.html @@ -22,5 +22,8 @@
+ +
+ - \ No newline at end of file + diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java index dde0d6f1718..c21e8792025 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java @@ -34,6 +34,7 @@ import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper; import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.to.SchemaTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.SchemaType; @@ -65,10 +66,14 @@ public abstract class AbstractAttrs extends Panel { protected final Map> membershipSchemas = new LinkedHashMap<>(); + protected final Map, Map> relationshipSchemas = new LinkedHashMap<>(); + protected final IModel> attrs; protected final IModel> membershipTOs; + protected final IModel> relationshipTOs; + private final List anyTypeClasses; public AbstractAttrs( @@ -81,6 +86,7 @@ public AbstractAttrs( this.anyTypeClasses = anyTypeClasses; this.attrs = new ListModel<>(List.of()); this.membershipTOs = new ListModel<>(List.of()); + this.relationshipTOs = new ListModel<>(List.of()); this.setOutputMarkupId(true); @@ -99,16 +105,13 @@ protected List loadAttrs() { return AbstractAttrs.this.getAttrsFromTO(); } - @SuppressWarnings({ "unchecked" }) protected List loadMembershipAttrs() { List memberships = new ArrayList<>(); membershipSchemas.clear(); for (MembershipTO membership : userTO.getMemberships()) { - setSchemas(Pair.of( - membership.getGroupKey(), membership.getGroupName()), - getMembershipAuxClasses(membership)); + setSchemas(membership, getMembershipAuxClasses(membership)); setAttrs(membership); if (AbstractAttrs.this instanceof PlainAttrs && !membership.getPlainAttrs().isEmpty()) { @@ -121,6 +124,25 @@ protected List loadMembershipAttrs() { return memberships; } + protected List loadRelationshipAttrs() { + List relationships = new ArrayList<>(); + + relationshipSchemas.clear(); + + for (RelationshipTO relationship : userTO.getRelationships()) { + setSchemas(relationship, getRelationshipAuxClasses(relationship)); + setAttrs(relationship); + + if (AbstractAttrs.this instanceof PlainAttrs && !relationship.getPlainAttrs().isEmpty()) { + relationships.add(relationship); + } else if (AbstractAttrs.this instanceof DerAttrs && !relationship.getDerAttrs().isEmpty()) { + relationships.add(relationship); + } + } + + return relationships; + } + protected boolean filterSchemas() { return !whichAttrs.isEmpty(); } @@ -150,23 +172,35 @@ protected List getDefaultValues(final String schema, final String groupN protected abstract SchemaType getSchemaType(); - protected void setSchemas(final Pair membership, final List anyTypeClasses) { + protected void setSchemas(final MembershipTO membership, final List anyTypeClasses) { + final Map mscs; + + if (membershipSchemas.containsKey(membership.getGroupKey())) { + mscs = membershipSchemas.get(membership.getGroupKey()); + } else { + mscs = new LinkedHashMap<>(); + membershipSchemas.put(membership.getGroupKey(), mscs); + } + setSchemas(anyTypeClasses, membership.getGroupName(), mscs); + } + + protected void setSchemas(final RelationshipTO relationship, final List anyTypeClasses) { final Map mscs; - if (membershipSchemas.containsKey(membership.getLeft())) { - mscs = membershipSchemas.get(membership.getLeft()); + if (relationshipSchemas.containsKey(Pair.of(relationship.getType(), relationship.getOtherEndKey()))) { + mscs = relationshipSchemas.get(Pair.of(relationship.getType(), relationship.getOtherEndKey())); } else { mscs = new LinkedHashMap<>(); - membershipSchemas.put(membership.getLeft(), mscs); + relationshipSchemas.put(Pair.of(relationship.getType(), relationship.getOtherEndKey()), mscs); } - setSchemas(anyTypeClasses, membership.getRight(), mscs); + setSchemas(anyTypeClasses, relationship.getOtherEndName(), mscs); } protected void setSchemas(final List anyTypeClasses) { setSchemas(anyTypeClasses, null, schemas); } - protected void setSchemas(final List anyTypeClasses, final String groupName, final Map scs) { + protected void setSchemas(final List anyTypeClasses, final String name, final Map scs) { final List allSchemas; if (anyTypeClasses.isEmpty()) { allSchemas = new ArrayList<>(); @@ -179,9 +213,9 @@ protected void setSchemas(final List anyTypeClasses, final String groupN if (filterSchemas()) { // 1. remove attributes not selected for display allSchemas.removeAll(allSchemas.stream(). - filter(schemaTO -> StringUtils.isBlank(groupName) + filter(schemaTO -> StringUtils.isBlank(name) ? !whichAttrs.containsKey(schemaTO.getKey()) - : !whichAttrs.containsKey(groupName + '#' + schemaTO.getKey())).collect(Collectors.toSet())); + : !whichAttrs.containsKey(name + '#' + schemaTO.getKey())).collect(Collectors.toSet())); } allSchemas.forEach(schemaTO -> scs.put(schemaTO.getKey(), schemaTO)); @@ -195,13 +229,23 @@ public boolean isPanelVisible() { protected abstract void setAttrs(MembershipTO membershipTO); + protected abstract void setAttrs(RelationshipTO membershipTO); + protected abstract List getAttrsFromTO(); protected abstract List getAttrsFromTO(MembershipTO membershipTO); protected List getMembershipAuxClasses(final MembershipTO membershipTO) { try { - return syncopeRestClient.searchUserTypeExtensions(membershipTO.getGroupName()); + return syncopeRestClient.readUserGroupTypeExtension(membershipTO.getGroupName()); + } catch (Exception e) { + return List.of(); + } + } + + protected List getRelationshipAuxClasses(final RelationshipTO relationshipTO) { + try { + return syncopeRestClient.readUserRelationshipTypeExtension(relationshipTO.getType()); } catch (Exception e) { return List.of(); } diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java index 63a5bc7051f..74076cbb9d5 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.layout.CustomizationOption; import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; @@ -32,8 +33,8 @@ import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.to.DerSchemaTO; -import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; @@ -125,7 +126,7 @@ protected void setAttrs() { @Override protected void setAttrs(final MembershipTO membershipTO) { - Map attrMap = GroupableRelatableTO.class.cast(userTO).getMembership(membershipTO.getGroupKey()). + Map attrMap = userTO.getMembership(membershipTO.getGroupKey()). map(gr -> EntityTOUtils.buildAttrMap(gr.getDerAttrs())). orElseGet(HashMap::new); @@ -143,6 +144,28 @@ protected void setAttrs(final MembershipTO membershipTO) { membershipTO.getDerAttrs().addAll(derAttrs); } + @Override + protected void setAttrs(final RelationshipTO relationshipTO) { + Map attrMap = userTO.getRelationship(relationshipTO.getType(), relationshipTO.getOtherEndKey()). + map(gr -> EntityTOUtils.buildAttrMap(gr.getDerAttrs())). + orElseGet(HashMap::new); + + List derAttrs = relationshipSchemas.get( + Pair.of(relationshipTO.getType(), relationshipTO.getOtherEndKey())).values().stream().map(schema -> { + + Attr attr = new Attr(); + attr.setSchema(schema.getKey()); + if (attrMap.containsKey(schema.getKey())) { + attr.getValues().addAll(attrMap.get(schema.getKey()).getValues()); + } + + return attr; + }).toList(); + + relationshipTO.getDerAttrs().clear(); + relationshipTO.getDerAttrs().addAll(derAttrs); + } + public static class DerSchemas extends Schemas { private static final long serialVersionUID = -4730563859116024676L; diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java index 7f97d2b2ecb..85e6c464e52 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java @@ -26,6 +26,7 @@ import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.FastDateFormat; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.layout.CustomizationOption; import org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel; @@ -45,9 +46,9 @@ import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.to.AnyTO; -import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.RelationshipTO; import org.apache.syncope.common.lib.types.AttrSchemaType; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.AttributeModifier; @@ -115,6 +116,35 @@ public WebMarkupContainer getPanel(final String panelId) { }), Model.of(-1)).setOutputMarkupId(true)); } }); + add(new ListView<>("relationshipsPlainSchemas", relationshipTOs) { + + private static final long serialVersionUID = 6741044372185745296L; + + @Override + protected void populateItem(final ListItem item) { + RelationshipTO relationshipTO = item.getModelObject(); + item.add(new Accordion("relationshipPlainSchemas", List.of(new AbstractTab( + new StringResourceModel( + "attributes.relationship.accordion", + PlainAttrs.this, + Model.of(relationshipTO))) { + + private static final long serialVersionUID = 1037272333056449378L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new PlainSchemas( + panelId, + relationshipTO.getOtherEndName(), + relationshipSchemas.get(Pair.of( + relationshipTO.getType(), relationshipTO.getOtherEndKey())), + new ListModel<>(relationshipTO.getPlainAttrs().stream(). + sorted(attrComparator). + collect(Collectors.toList()))); + } + }), Model.of(-1)).setOutputMarkupId(true)); + } + }); } @Override @@ -155,7 +185,7 @@ protected void setAttrs() { @Override protected void setAttrs(final MembershipTO membershipTO) { - Map attrMap = GroupableRelatableTO.class.cast(userTO).getMembership(membershipTO.getGroupKey()). + Map attrMap = userTO.getMembership(membershipTO.getGroupKey()). map(gr -> EntityTOUtils.buildAttrMap(gr.getPlainAttrs())). orElseGet(HashMap::new); @@ -176,6 +206,31 @@ protected void setAttrs(final MembershipTO membershipTO) { membershipTO.getPlainAttrs().addAll(plainAttrs); } + @Override + protected void setAttrs(final RelationshipTO relationshipTO) { + Map attrMap = userTO.getRelationship(relationshipTO.getType(), relationshipTO.getOtherEndKey()). + map(gr -> EntityTOUtils.buildAttrMap(gr.getPlainAttrs())). + orElseGet(HashMap::new); + + List plainAttrs = relationshipSchemas.get( + Pair.of(relationshipTO.getType(), relationshipTO.getOtherEndKey())).values().stream().map(schema -> { + + Attr attr = new Attr(); + attr.setSchema(schema.getKey()); + if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { + if (schema.getType() != AttrSchemaType.Dropdown || !schema.isMultivalue()) { + attr.getValues().add(StringUtils.EMPTY); + } + } else { + attr.getValues().addAll(attrMap.get(schema.getKey()).getValues()); + } + return attr; + }).toList(); + + relationshipTO.getPlainAttrs().clear(); + relationshipTO.getPlainAttrs().addAll(plainAttrs); + } + @SuppressWarnings("unchecked") protected AbstractFieldPanel getFieldPanel(final PlainSchemaTO plainSchema) { boolean required = plainSchema.getMandatoryCondition().equalsIgnoreCase("true"); diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java index 2ca11d8dc64..fef61c8d04e 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java @@ -25,12 +25,21 @@ public class SyncopeRestClient extends BaseRestClient { private static final long serialVersionUID = -2211371717449597247L; - public List searchUserTypeExtensions(final String groupName) { + public List readUserGroupTypeExtension(final String groupName) { try { - return getService(SyncopeService.class).readUserTypeExtension(groupName).getAuxClasses(); + return getService(SyncopeService.class).readUserGroupTypeExtension(groupName).getAuxClasses(); } catch (Exception e) { LOG.debug("While reading any type classes for type extension of group {}", groupName, e); return List.of(); } } + + public List readUserRelationshipTypeExtension(final String relationshipType) { + try { + return getService(SyncopeService.class).readUserRelationshipTypeExtension(relationshipType).getAuxClasses(); + } catch (Exception e) { + LOG.debug("While reading any type classes for type extension of relationship type {}", relationshipType, e); + return List.of(); + } + } } diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties index f6c0c024f65..200b1a020bd 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Own attributes.membership.accordion=From membership with '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties index 9b9f1a0c0b6..9599d5ce476 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties @@ -16,3 +16,5 @@ # under the License. attributes.accordion=Propri attributes.membership.accordion=Dalla membership con '${groupName}' +#Dalla relationship ${type} con '${otherEndName}' +attributes.relationship.accordion= diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties index 63f5ae6aa35..1125a339495 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=\u81ea\u5206 attributes.membership.accordion='${groupName}' \u3068\u306e\u30e1\u30f3\u30d0\u30fc\u30b7\u30c3\u30d7\u304b\u3089 +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties index f6c0c024f65..200b1a020bd 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=Own attributes.membership.accordion=From membership with '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties index 691a2c54482..c6c33f94e82 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties @@ -16,3 +16,4 @@ # under the License. attributes.accordion=\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 attributes.membership.accordion=\u0418\u0437 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u0433\u0440\u0443\u043f\u043f\u044b '${groupName}' +attributes.relationship.accordion=From relationship ${type} with '${otherEndName}' diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html index 9650ce51eb5..8d466ab6f5f 100644 --- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html +++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html @@ -21,7 +21,10 @@
-
+
+ + +
- \ No newline at end of file + diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java index fafebf7952a..1815e44c603 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/AnyOperations.java @@ -78,6 +78,41 @@ private static > K replacePatchItem( return proto; } + private static void relationships( + final RelatableTO updated, + final RelatableTO original, + final boolean incremental, + final Set updateReqs) { + + Map, RelationshipTO> updatedRels = + EntityTOUtils.buildRelationshipMap(updated.getRelationships()); + Map, RelationshipTO> originalRels = + EntityTOUtils.buildRelationshipMap(original.getRelationships()); + + updatedRels.forEach((pair, relationship) -> { + if (!originalRels.containsKey(pair) + || (originalRels.containsKey(pair) && !originalRels.get(pair).equals(relationship))) { + + RelationshipUR patch = new RelationshipUR.Builder(relationship.getType()). + otherEnd(relationship.getOtherEndKey()). + operation(PatchOperation.ADD_REPLACE).build(); + + patch.getPlainAttrs().addAll(relationship.getPlainAttrs().stream(). + filter(attr -> !isEmpty(attr)).toList()); + + updateReqs.add(patch); + } + }); + + if (!incremental) { + originalRels.keySet().stream().filter(pair -> !updatedRels.containsKey(pair)). + forEach(pair -> updateReqs.add( + new RelationshipUR.Builder(originalRels.get(pair).getType()). + otherEnd(originalRels.get(pair).getOtherEndKey()). + operation(PatchOperation.DELETE).build())); + } + } + private static void diff( final AnyTO updated, final AnyTO original, final AnyUR result, final boolean incremental) { @@ -92,7 +127,7 @@ private static void diff( // 1. realm result.setRealm(replacePatchItem(updated.getRealm(), original.getRealm(), new StringReplacePatchItem())); - // 2. auxilairy classes + // 2. auxiliary classes result.getAuxClasses().clear(); if (!incremental) { @@ -105,7 +140,10 @@ private static void diff( forEach(auxClass -> result.getAuxClasses().add(new StringPatchItem.Builder(). operation(PatchOperation.ADD_REPLACE).value(auxClass).build())); - // 3. plain attributes + // 3. relationships + relationships(updated, original, incremental, result.getRelationships()); + + // 4. plain attributes Map updatedAttrs = EntityTOUtils.buildAttrMap(updated.getPlainAttrs()); Map originalAttrs = EntityTOUtils.buildAttrMap(original.getPlainAttrs()); @@ -152,41 +190,6 @@ private static void diff( operation(PatchOperation.ADD_REPLACE).value(resource).build())); } - private static void relationships( - final RelatableTO updated, - final RelatableTO original, - final boolean incremental, - final Set updateReqs) { - - Map, RelationshipTO> updatedRels = - EntityTOUtils.buildRelationshipMap(updated.getRelationships()); - Map, RelationshipTO> originalRels = - EntityTOUtils.buildRelationshipMap(original.getRelationships()); - - updatedRels.forEach((pair, relationship) -> { - if (!originalRels.containsKey(pair) - || (originalRels.containsKey(pair) && !originalRels.get(pair).equals(relationship))) { - - RelationshipUR patch = new RelationshipUR.Builder(relationship.getType()). - otherEnd(relationship.getOtherEndType(), relationship.getOtherEndKey()). - operation(PatchOperation.ADD_REPLACE).build(); - - patch.getPlainAttrs().addAll(relationship.getPlainAttrs().stream(). - filter(attr -> !isEmpty(attr)).toList()); - - updateReqs.add(patch); - } - }); - - if (!incremental) { - originalRels.keySet().stream().filter(pair -> !updatedRels.containsKey(pair)). - forEach(pair -> updateReqs.add( - new RelationshipUR.Builder(originalRels.get(pair).getType()). - otherEnd(originalRels.get(pair).getOtherEndType(), originalRels.get(pair).getOtherEndKey()). - operation(PatchOperation.DELETE).build())); - } - } - private static void memberships( final GroupableRelatableTO updated, final GroupableRelatableTO original, @@ -234,10 +237,7 @@ public static AnyObjectUR diff( // 1. name result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem())); - // 2. relationships - relationships(updated, original, incremental, result.getRelationships()); - - // 3. memberships + // 2. memberships memberships(updated, original, incremental, result.getMemberships()); return result; @@ -296,13 +296,10 @@ public static UserUR diff(final UserTO updated, final UserTO original, final boo forEach(toAdd -> result.getRoles().add(new StringPatchItem.Builder(). operation(PatchOperation.ADD_REPLACE).value(toAdd).build())); - // 5. relationships - relationships(updated, original, incremental, result.getRelationships()); - - // 6. memberships + // 5. memberships memberships(updated, original, incremental, result.getMemberships()); - // 7. linked accounts + // 6. linked accounts Map, LinkedAccountTO> updatedAccounts = EntityTOUtils.buildLinkedAccountMap(updated.getLinkedAccounts()); Map, LinkedAccountTO> originalAccounts = @@ -351,9 +348,6 @@ public static GroupUR diff(final GroupTO updated, final GroupTO original, final // 4. type extensions result.getTypeExtensions().addAll(updated.getTypeExtensions()); - // 5. relationships - relationships(updated, original, incremental, result.getRelationships()); - return result; } @@ -449,19 +443,17 @@ public static AnyTO patch(final AnyTO anyTO, final AnyUR anyUR) { private static void relationships(final Set updateReqs, final RelatableTO relatable) { updateReqs.forEach(relPatch -> { - if (relPatch.getType() == null || relPatch.getOtherEndType() == null || relPatch.getOtherEndKey() == null) { + if (relPatch.getType() == null || relPatch.getOtherEndKey() == null) { LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch); } else { relatable.getRelationships().stream(). filter(relationship -> relPatch.getType().equals(relationship.getType()) - && relPatch.getOtherEndType().equals(relationship.getOtherEndType()) && relPatch.getOtherEndKey().equals(relationship.getOtherEndKey())). findFirst().ifPresent(memb -> relatable.getRelationships().remove(memb)); if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) { RelationshipTO newRelationshipTO = new RelationshipTO.Builder(relPatch.getType()). - otherEnd(relPatch.getOtherEndType(), relPatch.getOtherEndKey()). - // 3. plain attributes + otherEnd(relPatch.getOtherEndKey()). plainAttrs(relPatch.getPlainAttrs()). build(); diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java index d06a712a3ea..387de321a71 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/RelationshipUR.java @@ -44,9 +44,8 @@ protected RelationshipUR newInstance() { return new RelationshipUR(); } - public Builder otherEnd(final String type, final String key) { - getInstance().setOtherEndType(type); - getInstance().setOtherEndKey(key); + public Builder otherEnd(final String otherEndKey) { + getInstance().setOtherEndKey(otherEndKey); return this; } @@ -68,8 +67,6 @@ public Builder plainAttrs(final Collection plainAttrs) { private String type; - private String otherEndType; - private String otherEndKey; private final Set plainAttrs = new HashSet<>(); @@ -82,14 +79,6 @@ public void setType(final String type) { this.type = type; } - public String getOtherEndType() { - return otherEndType; - } - - public void setOtherEndType(final String otherEndType) { - this.otherEndType = otherEndType; - } - public String getOtherEndKey() { return otherEndKey; } @@ -109,7 +98,6 @@ public int hashCode() { return new HashCodeBuilder(). appendSuper(super.hashCode()). append(type). - append(otherEndType). append(otherEndKey). build(); } @@ -129,7 +117,6 @@ public boolean equals(final Object obj) { return new EqualsBuilder(). appendSuper(super.equals(obj)). append(type, other.type). - append(otherEndType, other.otherEndType). append(otherEndKey, other.otherEndKey). build(); } diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java index 575bf45429e..acb8cd9e769 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java @@ -33,7 +33,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind; @Schema(allOf = { AnyTO.class }) -public class GroupTO extends AnyTO { +public class GroupTO extends AnyTO implements TypeExtensionHolderTO { private static final long serialVersionUID = -7785920258290147542L; @@ -145,6 +145,7 @@ public Map getADynMembershipConds() { } @JsonIgnore + @Override public Optional getTypeExtension(final String anyType) { return typeExtensions.stream().filter( typeExtension -> anyType != null && anyType.equals(typeExtension.getAnyType())). @@ -153,6 +154,7 @@ public Optional getTypeExtension(final String anyType) { @JacksonXmlElementWrapper(localName = "typeExtensions") @JacksonXmlProperty(localName = "typeExtension") + @Override public List getTypeExtensions() { return typeExtensions; } diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java index 58645071cf0..2d8533d3047 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java @@ -57,6 +57,11 @@ public Builder(final String type, final End end) { instance.setEnd(end); } + public Builder otherEnd(final String otherEndKey) { + instance.setOtherEndKey(otherEndKey); + return this; + } + public Builder otherEnd(final String otherEndType, final String otherEndKey) { instance.setOtherEndType(otherEndType); instance.setOtherEndKey(otherEndKey); diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java index 87829232b63..06def5bd42f 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTypeTO.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Optional; -public class RelationshipTypeTO implements EntityTO { +public class RelationshipTypeTO implements TypeExtensionHolderTO, EntityTO { private static final long serialVersionUID = -1884088415277925817L; @@ -76,6 +76,7 @@ public void setRightEndAnyType(final String rightEndAnyType) { } @JsonIgnore + @Override public Optional getTypeExtension(final String anyType) { return typeExtensions.stream().filter( typeExtension -> anyType != null && anyType.equals(typeExtension.getAnyType())).findFirst(); @@ -83,6 +84,7 @@ public Optional getTypeExtension(final String anyType) { @JacksonXmlElementWrapper(localName = "typeExtensions") @JacksonXmlProperty(localName = "typeExtension") + @Override public List getTypeExtensions() { return typeExtensions; } diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/TypeExtensionHolderTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/TypeExtensionHolderTO.java new file mode 100644 index 00000000000..92faa9a8d24 --- /dev/null +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/TypeExtensionHolderTO.java @@ -0,0 +1,30 @@ +/* + * 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.common.lib.to; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +public interface TypeExtensionHolderTO extends Serializable { + + Optional getTypeExtension(String anyType); + + List getTypeExtensions(); +} diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java index 9cf16f03569..dabecbc65d7 100644 --- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java +++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java @@ -136,9 +136,20 @@ PagedResult searchAssignableGroups( * @return User type extension information, for the provided group */ @GET - @Path("userTypeExtension/{groupName}") + @Path("userTypeExtension/group/{groupName}") @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) - TypeExtensionTO readUserTypeExtension(@NotNull @PathParam("groupName") String groupName); + TypeExtensionTO readUserGroupTypeExtension(@NotNull @PathParam("groupName") String groupName); + + /** + * Extracts User type extension information, for the provided relationship type. + * + * @param relationshipType relationship type + * @return User type extension information, for the provided relationship type + */ + @GET + @Path("userTypeExtension/relationshipType/{relationshipType}") + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + TypeExtensionTO readUserRelationshipTypeExtension(@NotNull @PathParam("relationshipType") String relationshipType); /** * Exports the internal storage content as downloadable XML file. diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java index 4d20214dc19..25673963738 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java @@ -440,8 +440,10 @@ public SyncopeLogic syncopeLogic( final RealmSearchDAO realmSearchDAO, final AnyTypeDAO anyTypeDAO, final GroupDAO groupDAO, + final RelationshipTypeDAO relationshipTypeDAO, final AnySearchDAO anySearchDAO, final GroupDataBinder groupDataBinder, + final RelationshipTypeDataBinder relationshipTypeDataBinder, final ConfParamOps confParamOps, final ContentExporter exporter) { @@ -449,8 +451,10 @@ public SyncopeLogic syncopeLogic( realmSearchDAO, anyTypeDAO, groupDAO, + relationshipTypeDAO, anySearchDAO, groupDataBinder, + relationshipTypeDataBinder, confParamOps, exporter); } diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java index f9c85e23352..e400d584dee 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java @@ -35,14 +35,18 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO; +import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.search.AnyCond; import org.apache.syncope.core.persistence.api.dao.search.AttrCond; import org.apache.syncope.core.persistence.api.dao.search.SearchCond; import org.apache.syncope.core.persistence.api.entity.Realm; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.api.search.SyncopePage; import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; +import org.apache.syncope.core.provisioning.api.data.RelationshipTypeDataBinder; import org.apache.syncope.core.spring.security.AuthContextUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -58,10 +62,14 @@ public class SyncopeLogic extends AbstractLogic { protected final GroupDAO groupDAO; + protected final RelationshipTypeDAO relationshipTypeDAO; + protected final AnySearchDAO anySearchDAO; protected final GroupDataBinder groupDataBinder; + protected final RelationshipTypeDataBinder relationshipTypeDataBinder; + protected final ConfParamOps confParamOps; protected final ContentExporter exporter; @@ -70,16 +78,20 @@ public SyncopeLogic( final RealmSearchDAO realmSearchDAO, final AnyTypeDAO anyTypeDAO, final GroupDAO groupDAO, + final RelationshipTypeDAO relationshipTypeDAO, final AnySearchDAO anySearchDAO, final GroupDataBinder groupDataBinder, + final RelationshipTypeDataBinder relationshipTypeDataBinder, final ConfParamOps confParamOps, final ContentExporter exporter) { this.realmSearchDAO = realmSearchDAO; this.anyTypeDAO = anyTypeDAO; this.groupDAO = groupDAO; + this.relationshipTypeDAO = relationshipTypeDAO; this.anySearchDAO = anySearchDAO; this.groupDataBinder = groupDataBinder; + this.relationshipTypeDataBinder = relationshipTypeDataBinder; this.confParamOps = confParamOps; this.exporter = exporter; } @@ -125,14 +137,13 @@ public Page searchAssignableGroups( searchCond, pageable, AnyTypeKind.GROUP); - List result = matching.stream(). - map(group -> groupDataBinder.getGroupTO(group, false)).toList(); + List result = matching.stream().map(group -> groupDataBinder.getGroupTO(group, false)).toList(); return new SyncopePage<>(result, pageable, count); } @PreAuthorize("isAuthenticated()") - public TypeExtensionTO readTypeExtension(final String groupName) { + public TypeExtensionTO readUserGroupTypeExtension(final String groupName) { Group group = groupDAO.findByName(groupName). orElseThrow(() -> new NotFoundException("Group " + groupName)); @@ -142,6 +153,17 @@ public TypeExtensionTO readTypeExtension(final String groupName) { return groupDataBinder.getTypeExtensionTO(typeExt); } + @PreAuthorize("isAuthenticated()") + public TypeExtensionTO readUserRelationshipTypeExtension(final String relationshipType) { + RelationshipType rt = relationshipTypeDAO.findById(relationshipType). + orElseThrow(() -> new NotFoundException("RelationshipType " + relationshipType)); + + RelationshipTypeExtension typeExt = rt.getTypeExtension(anyTypeDAO.getUser()). + orElseThrow(() -> new NotFoundException("TypeExtension in " + relationshipType + " for users")); + + return relationshipTypeDataBinder.getTypeExtensionTO(typeExt); + } + @PreAuthorize("hasRole('" + IdRepoEntitlement.KEYMASTER + "')") @Transactional(readOnly = true) public void exportInternalStorageContent( diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java index 065261b22d3..a275830aaf6 100644 --- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java +++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java @@ -95,8 +95,13 @@ public PagedResult searchAssignableGroups( } @Override - public TypeExtensionTO readUserTypeExtension(final String groupName) { - return logic.readTypeExtension(groupName); + public TypeExtensionTO readUserGroupTypeExtension(final String groupName) { + return logic.readUserGroupTypeExtension(groupName); + } + + @Override + public TypeExtensionTO readUserRelationshipTypeExtension(final String relationshipType) { + return logic.readUserRelationshipTypeExtension(relationshipType); } private DestinationRegistry getDestinationRegistryFromBusOrDefault() { diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java index 6671b3443ce..21416f43894 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java @@ -156,7 +156,8 @@ public boolean add(final PlainAttr attr) { @Override public boolean remove(final PlainAttr attr) { return plainAttrsList.removeIf(a -> a.getSchema().equals(attr.getSchema()) - && Objects.equals(a.getMembership(), attr.getMembership())); + && Objects.equals(a.getMembership(), attr.getMembership()) + && Objects.equals(a.getRelationship(), attr.getRelationship())); } @Override diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java index a99a87b62ae..a194df71467 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java @@ -195,19 +195,22 @@ public boolean add(final PlainAttr attr) { @Override public boolean remove(final PlainAttr attr) { - return plainAttrsList.removeIf(a -> a.getSchema().equals(attr.getSchema())); + return plainAttrsList.removeIf(a -> a.getSchema().equals(attr.getSchema()) + && Objects.equals(a.getRelationship(), attr.getRelationship())); } @Override public Optional getPlainAttr(final String plainSchema) { return plainAttrsList.stream(). - filter(attr -> plainSchema.equals(attr.getSchema())). + filter(attr -> attr.getRelationship() == null && plainSchema.equals(attr.getSchema())). findFirst(); } @Override public List getPlainAttrs() { - return plainAttrsList.stream().toList(); + return plainAttrsList.stream(). + filter(attr -> attr.getRelationship() == null). + toList(); } @Override diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java index 196c7a91978..7454e5eb9df 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java @@ -286,7 +286,8 @@ public boolean add(final PlainAttr attr) { @Override public boolean remove(final PlainAttr attr) { return plainAttrsList.removeIf(a -> a.getSchema().equals(attr.getSchema()) - && Objects.equals(a.getMembership(), attr.getMembership())); + && Objects.equals(a.getMembership(), attr.getMembership()) + && Objects.equals(a.getRelationship(), attr.getRelationship())); } @Override diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RelationshipTypeDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RelationshipTypeDataBinder.java index 3e940eecba5..1a2d6ea3bb1 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RelationshipTypeDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RelationshipTypeDataBinder.java @@ -19,7 +19,9 @@ package org.apache.syncope.core.provisioning.api.data; import org.apache.syncope.common.lib.to.RelationshipTypeTO; +import org.apache.syncope.common.lib.to.TypeExtensionTO; import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.RelationshipTypeExtension; public interface RelationshipTypeDataBinder { @@ -27,5 +29,7 @@ public interface RelationshipTypeDataBinder { void update(RelationshipType relationshipType, RelationshipTypeTO relationshipTypeTO); + TypeExtensionTO getTypeExtensionTO(RelationshipTypeExtension typeExt); + RelationshipTypeTO getRelationshipTypeTO(RelationshipType relationshipType); } 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 305e23983f2..1563e9cba1e 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 @@ -45,7 +45,6 @@ import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.Provision; import org.apache.syncope.common.lib.to.RelationshipTO; -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; @@ -62,7 +61,6 @@ import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.Any; -import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.AnyUtils; import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; @@ -422,8 +420,7 @@ protected void fill( // 2. relationships Set> relationships = new HashSet<>(); for (RelationshipUR patch : anyUR.getRelationships().stream(). - filter(patch -> patch.getType() != null - && patch.getOtherEndType() != null && patch.getOtherEndKey() != null).toList()) { + filter(patch -> patch.getType() != null && patch.getOtherEndKey() != null).toList()) { RelationshipType relationshipType = relationshipTypeDAO.findById(patch.getType()).orElse(null); if (relationshipType == null) { @@ -444,41 +441,33 @@ protected void fill( } case ADD_REPLACE -> { - if (AnyTypeKind.USER.name().equals(patch.getOtherEndType()) - || AnyTypeKind.GROUP.name().equals(patch.getOtherEndType())) { - - SyncopeClientException invalidAnyType = - SyncopeClientException.build(ClientExceptionType.InvalidAnyType); - invalidAnyType.getElements().add(AnyType.class.getSimpleName() - + " not allowed for relationship: " + patch.getOtherEndType()); - scce.addException(invalidAnyType); + AnyObject otherEnd = anyObjectDAO.findById(patch.getOtherEndKey()).orElse(null); + if (otherEnd == null) { + LOG.debug("Ignoring invalid any object {}", patch.getOtherEndKey()); + } else if (!relationshipType.getRightEndAnyType().equals(otherEnd.getType())) { + LOG.debug("Ignoring mismatching anyType {}", otherEnd.getType().getKey()); + } else if (relationships.contains(Pair.of(relationshipType.getKey(), otherEnd.getKey()))) { + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() + + " in relationship " + relationshipType.getKey()); + scce.addException(assigned); } else { - AnyObject otherEnd = anyObjectDAO.findById(patch.getOtherEndKey()).orElse(null); - if (otherEnd == null) { - LOG.debug("Ignoring invalid any object {}", patch.getOtherEndKey()); - } else if (relationships.contains(Pair.of(relationshipType.getKey(), otherEnd.getKey()))) { - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() - + " in relationship " + relationshipType.getKey()); - scce.addException(assigned); - } else { - relationships.add(Pair.of(relationshipType.getKey(), otherEnd.getKey())); + relationships.add(Pair.of(relationshipType.getKey(), otherEnd.getKey())); - Relationship relationship = any.getRelationship( - relationshipType, patch.getOtherEndKey()).orElse(null); - if (relationship == null) { - relationship = anyUtils.add(any, relationshipType, otherEnd); - } else { - any.getPlainAttrs(relationship).forEach(any::remove); - } + Relationship relationship = any.getRelationship( + relationshipType, patch.getOtherEndKey()).orElse(null); + if (relationship == null) { + relationship = anyUtils.add(any, relationshipType, otherEnd); + } else { + any.getPlainAttrs(relationship).forEach(any::remove); + } - // relationship attributes - relationshipPlainAttrsOnUpdate(patch.getPlainAttrs(), anyTO, any, relationship, scce); + // relationship attributes + relationshipPlainAttrsOnUpdate(patch.getPlainAttrs(), anyTO, any, relationship, scce); - propByRes.addAll( - ResourceOperation.UPDATE, anyObjectDAO.findAllResourceKeys(otherEnd.getKey())); - } + propByRes.addAll( + ResourceOperation.UPDATE, anyObjectDAO.findAllResourceKeys(otherEnd.getKey())); } } @@ -627,44 +616,33 @@ protected void fill( // 1. relationships Set> relationships = new HashSet<>(); anyCR.getRelationships().forEach(relationshipTO -> { - if (StringUtils.isBlank(relationshipTO.getOtherEndType()) - || AnyTypeKind.USER.name().equals(relationshipTO.getOtherEndType()) - || AnyTypeKind.GROUP.name().equals(relationshipTO.getOtherEndType())) { - - SyncopeClientException invalidAnyType = - SyncopeClientException.build(ClientExceptionType.InvalidAnyType); - invalidAnyType.getElements().add(AnyType.class.getSimpleName() - + " not allowed for relationship: " + relationshipTO.getOtherEndType()); - scce.addException(invalidAnyType); + RelationshipType relationshipType = relationshipTypeDAO.findById(relationshipTO.getType()).orElse(null); + AnyObject otherEnd = anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null); + if (relationshipType == null) { + LOG.debug("Ignoring invalid relationship type {}", relationshipTO.getType()); + } else if (otherEnd == null) { + LOG.debug("Ignoring invalid anyObject {}", relationshipTO.getOtherEndKey()); + } else if (!relationshipType.getRightEndAnyType().equals(otherEnd.getType())) { + LOG.debug("Ignoring mismatching anyType {}", relationshipTO.getOtherEndType()); + } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) { + SyncopeClientException noRight = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + noRight.getElements().add( + "Relationships shall be created or updated only from their left end"); + scce.addException(noRight); + } else if (relationships.contains(Pair.of(otherEnd.getKey(), relationshipType.getKey()))) { + SyncopeClientException assigned = + SyncopeClientException.build(ClientExceptionType.InvalidRelationship); + assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() + + " in relationship " + relationshipTO.getType()); + scce.addException(assigned); } else { - RelationshipType relationshipType = relationshipTypeDAO.findById(relationshipTO.getType()).orElse(null); - AnyObject otherEnd = anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null); - if (relationshipType == null) { - LOG.debug("Ignoring invalid relationship type {}", relationshipTO.getType()); - } else if (otherEnd == null) { - LOG.debug("Ignoring invalid anyObject {}", relationshipTO.getOtherEndKey()); - } else if (!relationshipTO.getOtherEndType().equals(otherEnd.getType().getKey())) { - LOG.debug("Ignoring mismatching anyType {}", relationshipTO.getOtherEndType()); - } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) { - SyncopeClientException noRight = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - noRight.getElements().add( - "Relationships shall be created or updated only from their left end"); - scce.addException(noRight); - } else if (relationships.contains(Pair.of(otherEnd.getKey(), relationshipType.getKey()))) { - SyncopeClientException assigned = - SyncopeClientException.build(ClientExceptionType.InvalidRelationship); - assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName() - + " in relationship " + relationshipTO.getType()); - scce.addException(assigned); - } else { - relationships.add(Pair.of(otherEnd.getKey(), relationshipType.getKey())); + relationships.add(Pair.of(otherEnd.getKey(), relationshipType.getKey())); - Relationship relationship = anyUtils.add(any, relationshipType, otherEnd); + Relationship relationship = anyUtils.add(any, relationshipType, otherEnd); - // relationship attributes - relationshipPlainAttrsOnCreate(anyTO, any, relationship, relationshipTO, scce); - } + // relationship attributes + relationshipPlainAttrsOnCreate(anyTO, any, relationship, relationshipTO, scce); } }); diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java index 4cd1f4db17b..5808882ca00 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RelationshipTypeDataBinderImpl.java @@ -115,7 +115,8 @@ public void update(final RelationshipType relationshipType, final RelationshipTy removeIf(typeExt -> relationshipTypeTO.getTypeExtension(typeExt.getAnyType().getKey()).isEmpty()); } - protected TypeExtensionTO getTypeExtensionTO(final RelationshipTypeExtension typeExt) { + @Override + public TypeExtensionTO getTypeExtensionTO(final RelationshipTypeExtension typeExt) { TypeExtensionTO typeExtTO = new TypeExtensionTO(); typeExtTO.setAnyType(typeExt.getAnyType().getKey()); typeExtTO.getAuxClasses().addAll(typeExt.getAuxClasses().stream().map(AnyTypeClass::getKey).toList()); diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java index f633fa2a41e..78ffec858cf 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderTest.java @@ -159,7 +159,7 @@ public void relationshipWithAttr() { // then add relationship with attribute UserUR userUR = new UserUR.Builder("1417acbe-cbf6-4277-9372-e75e04f97000"). relationship(new RelationshipUR.Builder("neighborhood"). - otherEnd("PRINTER", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). + otherEnd("8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). plainAttr(new Attr.Builder("obscure").value("testvalue3").build()). build()). build(); diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java index cfd9c9fc173..28585e2f24f 100644 --- a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java +++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java @@ -51,8 +51,7 @@ protected void doExecute(final DelegateExecution execution) { UserUR userUR = new UserUR(); userUR.setKey(user.getKey()); - userUR.getRelationships().add(new RelationshipUR.Builder("neighborhood"). - otherEnd("PRINTER", printer).build()); + userUR.getRelationships().add(new RelationshipUR.Builder("neighborhood").otherEnd(printer).build()); UserWorkflowResult.PropagationInfo propInfo = dataBinder.update(user, userUR); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java index 056be0c7bdc..fede1e77397 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java @@ -780,23 +780,13 @@ public void issueSYNCOPE1030() { + "outerObjectsRepeater:0:outer:form:content:form:view:username:textField", TextField.class); - formTester = TESTER.newFormTester( - "body:content:body:container:content:tabbedPanel:panel:searchResult:" - + "outerObjectsRepeater:0:outer:form:content:form"); - assertNotNull(formTester); - formTester.submit("buttons:next"); - - formTester = TESTER.newFormTester( - "body:content:body:container:content:tabbedPanel:panel:searchResult:" - + "outerObjectsRepeater:0:outer:form:content:form"); - assertNotNull(formTester); - formTester.submit("buttons:next"); - - formTester = TESTER.newFormTester( - "body:content:body:container:content:tabbedPanel:panel:searchResult:" - + "outerObjectsRepeater:0:outer:form:content:form"); - assertNotNull(formTester); - formTester.submit("buttons:next"); + for (int i = 0; i < 4; i++) { + formTester = TESTER.newFormTester( + "body:content:body:container:content:tabbedPanel:panel:searchResult:" + + "outerObjectsRepeater:0:outer:form:content:form"); + assertNotNull(formTester); + formTester.submit("buttons:next"); + } formTester = TESTER.newFormTester( "body:content:body:container:content:tabbedPanel:panel:searchResult:" diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RelationshipTypesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RelationshipTypesITCase.java index 559b3d4299c..681ff77fad7 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RelationshipTypesITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RelationshipTypesITCase.java @@ -128,11 +128,11 @@ public void delete() { TESTER.clickLink(TESTER.getComponentFromLastRenderedPage( "body:content:tabbedPanel:panel:outerObjectsRepeater:1:outer:container:content:" - + "togglePanelContainer:container:actions:actions:actionRepeater:1:action:action")); + + "togglePanelContainer:container:actions:actions:actionRepeater:2:action:action")); TESTER.executeAjaxEvent(TESTER.getComponentFromLastRenderedPage( "body:content:tabbedPanel:panel:outerObjectsRepeater:1:outer:container:content:" - + "togglePanelContainer:container:actions:actions:actionRepeater:1:action:action"), Constants.ON_CLICK); + + "togglePanelContainer:container:actions:actions:actionRepeater:2:action:action"), Constants.ON_CLICK); assertSuccessMessage(); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java index fcae9ee53e8..6d9e17405cb 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java @@ -198,8 +198,7 @@ public void editUserMembership() { + "outerObjectsRepeater:1:outer:container:content:togglePanelContainer:container:" + "actions:actions:actionRepeater:0:action:action"); - FormTester formTester = TESTER.newFormTester(TAB_PANEL - + "outerObjectsRepeater:0:outer:form:content:form"); + FormTester formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); assertNotNull(formTester); formTester.submit("buttons:next"); @@ -214,6 +213,11 @@ public void editUserMembership() { TESTER.executeAjaxEvent( TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form:buttons:next", Constants.ON_CLICK); + // skip relationships + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); + assertNotNull(formTester); + formTester.submit("buttons:next"); + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); assertNotNull(formTester); @@ -306,6 +310,11 @@ public void editUserMemberships() { + "outerObjectsRepeater:0:outer:form:content:form:buttons:next", Constants.ON_CLICK); + // advance relationships + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); + assertNotNull(formTester); + formTester.submit("buttons:next"); + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); assertNotNull(formTester); @@ -382,6 +391,11 @@ public void editUserMemberships() { + "outerObjectsRepeater:0:outer:form:content:form:buttons:next", Constants.ON_CLICK); + // advance relationships + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); + assertNotNull(formTester); + formTester.submit("buttons:next"); + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); assertNotNull(formTester); @@ -465,6 +479,11 @@ public void editUserMemberships() { + "outerObjectsRepeater:0:outer:form:content:form:buttons:next", Constants.ON_CLICK); + // skip relationships + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); + assertNotNull(formTester); + formTester.submit("buttons:next"); + formTester = TESTER.newFormTester(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form"); assertNotNull(formTester); 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 c4d2fe5ee4e..d011ca81892 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 @@ -37,8 +37,6 @@ import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.ConnObject; import org.apache.syncope.common.lib.to.PagedResult; -import org.apache.syncope.common.lib.to.RelationshipTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.SchemaType; @@ -183,20 +181,6 @@ public void deleteAttr() { } } - @Test - public void issueSYNCOPE756() { - AnyObjectCR anyObjectCR = getSample("issueSYNCOPE756"); - anyObjectCR.getRelationships().add(new RelationshipTO.Builder("neighborhood").otherEnd( - AnyTypeKind.USER.name(), "1417acbe-cbf6-4277-9372-e75e04f97000").build()); - - try { - createAnyObject(anyObjectCR).getEntity(); - fail("This should not happen"); - } catch (SyncopeClientException e) { - assertEquals(ClientExceptionType.InvalidAnyType, e.getType()); - } - } - @Test public void issueSYNCOPE1472() { // 1. assign resource-db-scripted again to Canon MF 8030cn and update twice diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java index 73da3ef217c..1c2c80fa7b9 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RelationshipITCase.java @@ -27,8 +27,10 @@ import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.request.AnyObjectCR; import org.apache.syncope.common.lib.request.AnyObjectUR; +import org.apache.syncope.common.lib.request.AttrPatch; import org.apache.syncope.common.lib.request.RelationshipUR; 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.RelationshipTO; import org.apache.syncope.common.lib.to.RelationshipTypeTO; @@ -59,8 +61,7 @@ public void unlimitedRelationships() { assertEquals(left.getKey(), right.getRelationships().getFirst().getOtherEndKey()); AnyObjectUR anyObjectUR = new AnyObjectUR.Builder(left.getKey()). - relationship(new RelationshipUR.Builder("inclusion"). - otherEnd(right.getType(), right.getKey()).build()).build(); + relationship(new RelationshipUR.Builder("inclusion").otherEnd(right.getKey()).build()).build(); left = updateAnyObject(anyObjectUR).getEntity(); assertEquals(2, left.getRelationships().size()); assertTrue(left.getRelationships().stream().anyMatch(r -> right.getKey().equals(r.getOtherEndKey()))); @@ -85,7 +86,7 @@ public void relationshipWithAttr() { // then add relationship with attribute UserCR userCR = UserITCase.getUniqueSample("relationshipWithAttr@syncope.apache.org"); userCR.getRelationships().add(new RelationshipTO.Builder("neighborhood"). - otherEnd("PRINTER", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). + otherEnd("8559d14d-58c2-46eb-a2d4-a7d35161e8f8"). plainAttr(new Attr.Builder("obscure").value("testvalue3").build()). build()); @@ -96,6 +97,27 @@ public void relationshipWithAttr() { assertEquals(1, rel.getPlainAttr("obscure").orElseThrow().getValues().size()); assertEquals(1, rel.getDerAttrs().size()); assertEquals(1, rel.getDerAttr("noschema").orElseThrow().getValues().size()); + + // finally add another relationship attribute + UserUR req = new UserUR(); + req.setKey(user.getKey()); + req.getPlainAttrs().add(new AttrPatch.Builder(new Attr.Builder("aLong").value("5678").build()).build()); + req.getRelationships().add(new RelationshipUR.Builder("neighborhood"). + otherEnd(rel.getOtherEndKey()). + plainAttr(new Attr.Builder("aLong").value("1234").build()). + plainAttrs(rel.getPlainAttrs()). + build()); + + user = updateUser(req).getEntity(); + + assertEquals("5678", user.getPlainAttr("aLong").orElseThrow().getValues().getFirst()); + + rel = user.getRelationship("neighborhood", "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").orElseThrow(); + assertEquals(2, rel.getPlainAttrs().size()); + assertEquals(1, rel.getPlainAttr("obscure").orElseThrow().getValues().size()); + assertEquals("1234", rel.getPlainAttr("aLong").orElseThrow().getValues().getFirst()); + assertEquals(1, rel.getDerAttrs().size()); + assertEquals(1, rel.getDerAttr("noschema").orElseThrow().getValues().size()); } @Test @@ -115,10 +137,10 @@ public void issueSYNCOPE1686() { // Add relationships: printer1 -> printer2 and printer2 -> printer3 AnyObjectUR relationship1To2 = new AnyObjectUR.Builder(key1) - .relationship(new RelationshipUR.Builder("inclusion").otherEnd(PRINTER, key2).build()) + .relationship(new RelationshipUR.Builder("inclusion").otherEnd(key2).build()) .build(); AnyObjectUR relationship2To3 = new AnyObjectUR.Builder(key2) - .relationship(new RelationshipUR.Builder("inclusion").otherEnd(PRINTER, key3).build()) + .relationship(new RelationshipUR.Builder("inclusion").otherEnd(key3).build()) .build(); updateAnyObject(relationship1To2); diff --git a/pom.xml b/pom.xml index c6f74c2c90a..c50f8c03c68 100644 --- a/pom.xml +++ b/pom.xml @@ -857,6 +857,11 @@ under the License. tomcat-embed-el ${tomcat.version} + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcat.version} + org.apache.cxf From a1dfd53328903641e8f872106536f1abcdb3b104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 18 Dec 2025 12:31:58 +0100 Subject: [PATCH 6/7] Docs --- .../concepts/typemanagement.adoc | 114 +++++++++++++++++- 1 file changed, 108 insertions(+), 6 deletions(-) diff --git a/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc b/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc index 6a279c5138c..426e5710928 100644 --- a/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc +++ b/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc @@ -184,13 +184,58 @@ increase readability): ==== RelationshipType -Relationships allow the creation of a link between a user, a group or an any object with an any object; relationship -types define the available link types. +Relationships allow the creation of a link between a user, a group or an any object with another any object; +relationship types define the available link types, including: -.Relationship between Any Objects (printers) +* name +* description +* what any type shall be on the left side +* what any type shall be on the right side +* the related <> + +.RelationshipType definitions ==== -The following any object of type `PRINTER` contains a relationship of type `neighbourhood` with another `PRINTER` -(details are simplified to increase readability): +The following shows two definitions: + +. `inclusion`: between `PRINTER` and `PRINTER`, with no type extension +. `neighborhood`: between `USER` and `PRINTER`, with type extension of the `other` any type class. + +[source,json] +---- +[ + { + "key": "inclusion", + "description": "Models the act that a printer is included by another printer", + "leftEndAnyType": "PRINTER", + "rightEndAnyType": "PRINTER", + "typeExtensions": [] + }, + { + "key": "neighborhood", + "description": "Models the act that an user is near a printer", + "leftEndAnyType": "USER", + "rightEndAnyType": "PRINTER", + "typeExtensions": [ + { + "anyType": "USER", + "auxClasses": [ + "other" + ] + } + ] + } +] +---- +==== + +.Relationships for Any Object +==== +The following any object of type `PRINTER` contains (details are simplified to increase readability): + +* a relationship of type `inclusion` with another `PRINTER` named `Canon MF 8030cn`, where this any object is on the +left side of the relationship +* a relationship of type `neighborhood` with the user `bellini`, where this any object is on the +right side of the relationship [source,json] ---- @@ -216,7 +261,7 @@ The following any object of type `PRINTER` contains a relationship of type `neig ], "relationships": [ { - "type": "neighborhood", + "type": "inclusion", "end": "LEFT", "otherEndType": "PRINTER", "otherEndKey": "8559d14d-58c2-46eb-a2d4-a7d35161e8f8", @@ -234,8 +279,52 @@ The following any object of type `PRINTER` contains a relationship of type `neig ---- ==== +.Relationship for User +==== +The following user contains a relationship of type `neighborhood` with the `PRINTER` `HP LJ 1300n`, bearing attributes +(details are simplified to increase readability): + +[source,json] +---- +{ + "key": "c9b2dec2-00a7-4855-97c0-d854842b4b24", + "type": "USER", + "realm": "/", + "username": "bellini", + "relationships": [ + { + "type": "neighborhood", + "end": "LEFT", + "otherEndType": "PRINTER", + "otherEndKey": "fc6dbc3a-6c07-4965-8781-921e7401a4a5", + "otherEndName": "HP LJ 1300n", + "plainAttrs": [ + { + "schema": "activationDate", + "values": [ + "2025-12-18T12:30:00.000+0000" + ] + } + ], + "derAttrs": [ + { + "schema": "noschema", + "values": [ + ", " + ] + } + ] + } + ] +} +---- +==== + ==== Type Extensions +[discrete] +===== Memberships + When a user (or an any object) is part of a group, a _membership_ is defined. It is sometimes useful to define attributes which are bound to a particular membership: if, for example, the @@ -289,3 +378,16 @@ With reference to the sample above (details are simplified to increase readabili } ---- ==== + +[discrete] +===== Relationships + +When a user (or a group or an any object) is linked to another any object according to a given +<>, a _relationship_ is defined. + +As already indicated above for memberships, it is sometimes useful to define attributes which are bound to a particular +relationship. + +Type extensions define a set of <> associated to a relationship type, that can be automatically +assigned to a given user (or group or any object) when entering in a relationship of the given type with another +any object. From 53b7d10bc0e360442b03f0570a2958a5bc419dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 18 Dec 2025 13:29:47 +0100 Subject: [PATCH 7/7] Upgrading Spring Boot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c50f8c03c68..e819ce0c77f 100644 --- a/pom.xml +++ b/pom.xml @@ -435,7 +435,7 @@ under the License. 1.83 10.6 - 3.5.8 + 3.5.9 4.3.3 4.1.1