
org.apache.syncope.common.lib.AnyOperations Maven / Gradle / Ivy
/*
* 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;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.patch.AnyObjectPatch;
import org.apache.syncope.common.lib.patch.AnyPatch;
import org.apache.syncope.common.lib.patch.AttrPatch;
import org.apache.syncope.common.lib.patch.GroupPatch;
import org.apache.syncope.common.lib.patch.MembershipPatch;
import org.apache.syncope.common.lib.patch.PasswordPatch;
import org.apache.syncope.common.lib.patch.RelationshipPatch;
import org.apache.syncope.common.lib.patch.AbstractReplacePatchItem;
import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
import org.apache.syncope.common.lib.patch.LinkedAccountPatch;
import org.apache.syncope.common.lib.patch.StringPatchItem;
import org.apache.syncope.common.lib.patch.StringReplacePatchItem;
import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTO;
import org.apache.syncope.common.lib.to.AttrTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.LinkedAccountTO;
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.PatchOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class for comparing {@link AnyTO} instances in order to generate {@link AnyPatch} instances.
*/
public final class AnyOperations {
private static final Logger LOG = LoggerFactory.getLogger(AnyOperations.class);
private static final Set NULL_SINGLETON = Collections.singleton(null);
private AnyOperations() {
// empty constructor for static utility classes
}
private static > K replacePatchItem(
final T updated, final T original, final K proto) {
if ((original == null && updated == null) || (original != null && original.equals(updated))) {
return null;
}
proto.setValue(updated);
return proto;
}
private static void diff(
final AnyTO updated, final AnyTO original, final AnyPatch result, final boolean incremental) {
// check same key
if (updated.getKey() == null && original.getKey() != null
|| (updated.getKey() != null && !updated.getKey().equals(original.getKey()))) {
throw new IllegalArgumentException("AnyTO's key must be the same");
}
result.setKey(updated.getKey());
// 1. realm
result.setRealm(replacePatchItem(updated.getRealm(), original.getRealm(), new StringReplacePatchItem()));
// 2. auxilairy classes
result.getAuxClasses().clear();
if (!incremental) {
original.getAuxClasses().stream().filter(auxClass -> !updated.getAuxClasses().contains(auxClass)).
forEach(auxClass -> {
result.getAuxClasses().add(new StringPatchItem.Builder().
operation(PatchOperation.DELETE).value(auxClass).build());
});
}
updated.getAuxClasses().stream().filter(auxClass -> !original.getAuxClasses().contains(auxClass)).
forEach(auxClass -> {
result.getAuxClasses().add(new StringPatchItem.Builder().
operation(PatchOperation.ADD_REPLACE).value(auxClass).build());
});
// 3. plain attributes
Map updatedAttrs = EntityTOUtils.buildAttrMap(updated.getPlainAttrs());
Map originalAttrs = EntityTOUtils.buildAttrMap(original.getPlainAttrs());
result.getPlainAttrs().clear();
if (!incremental) {
originalAttrs.keySet().stream().filter(attr -> !updatedAttrs.containsKey(attr)).
forEach(schema -> {
result.getPlainAttrs().add(new AttrPatch.Builder().
operation(PatchOperation.DELETE).
attrTO(new AttrTO.Builder().schema(schema).build()).
build());
});
}
updatedAttrs.values().forEach(attrTO -> {
if (isEmpty(attrTO)) {
if (!incremental) {
result.getPlainAttrs().add(new AttrPatch.Builder().
operation(PatchOperation.DELETE).
attrTO(new AttrTO.Builder().schema(attrTO.getSchema()).build()).
build());
}
} else if (!originalAttrs.containsKey(attrTO.getSchema())
|| !originalAttrs.get(attrTO.getSchema()).getValues().equals(attrTO.getValues())) {
AttrPatch patch = new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).attrTO(attrTO).
build();
if (!patch.isEmpty()) {
result.getPlainAttrs().add(patch);
}
}
});
// 4. virtual attributes
result.getVirAttrs().clear();
result.getVirAttrs().addAll(updated.getVirAttrs());
// 5. resources
result.getResources().clear();
if (!incremental) {
original.getResources().stream().filter(resource -> !updated.getResources().contains(resource)).
forEach(resource -> {
result.getResources().add(new StringPatchItem.Builder().
operation(PatchOperation.DELETE).value(resource).build());
});
}
updated.getResources().stream().filter(resource -> !original.getResources().contains(resource)).
forEach(resource -> {
result.getResources().add(new StringPatchItem.Builder().
operation(PatchOperation.ADD_REPLACE).value(resource).build());
});
}
/**
* Calculate modifications needed by first in order to be equal to second.
*
* @param updated updated AnyObjectTO
* @param original original AnyObjectTO
* @param incremental perform incremental diff (without removing existing info)
* @return AnyObjectPatch containing differences
*/
public static AnyObjectPatch diff(
final AnyObjectTO updated, final AnyObjectTO original, final boolean incremental) {
AnyObjectPatch result = new AnyObjectPatch();
diff(updated, original, result, incremental);
// 1. name
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()))).
forEachOrdered(entry -> {
result.getRelationships().add(new RelationshipPatch.Builder().
operation(PatchOperation.ADD_REPLACE).
relationshipTO(entry.getValue()).build());
});
if (!incremental) {
originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)).
forEach(key -> {
result.getRelationships().add(new RelationshipPatch.Builder().
operation(PatchOperation.DELETE).
relationshipTO(originalRels.get(key)).build());
});
}
// 3. memberships
Map updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships());
Map originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships());
updatedMembs.forEach((key, value) -> {
MembershipPatch membershipPatch = new MembershipPatch.Builder().
operation(PatchOperation.ADD_REPLACE).group(value.getGroupKey()).build();
diff(value, membershipPatch);
if (!originalMembs.containsKey(key)
|| (!membershipPatch.getPlainAttrs().isEmpty() || !membershipPatch.getVirAttrs().isEmpty())) {
result.getMemberships().add(membershipPatch);
}
});
if (!incremental) {
originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)).
forEach(key -> {
result.getMemberships().add(new MembershipPatch.Builder().
operation(PatchOperation.DELETE).group(originalMembs.get(key).getGroupKey()).build());
});
}
return result;
}
private static void diff(
final MembershipTO updated,
final MembershipPatch result) {
// 1. plain attributes
result.getPlainAttrs().addAll(updated.getPlainAttrs().stream().
filter(attrTO -> !isEmpty(attrTO)).
collect(Collectors.toSet()));
// 2. virtual attributes
result.getVirAttrs().clear();
result.getVirAttrs().addAll(updated.getVirAttrs());
}
/**
* Calculate modifications needed by first in order to be equal to second.
*
* @param updated updated UserTO
* @param original original UserTO
* @param incremental perform incremental diff (without removing existing info)
* @return UserPatch containing differences
*/
public static UserPatch diff(final UserTO updated, final UserTO original, final boolean incremental) {
UserPatch result = new UserPatch();
diff(updated, original, result, incremental);
// 1. password
if (updated.getPassword() != null
&& (original.getPassword() == null || !original.getPassword().equals(updated.getPassword()))) {
result.setPassword(new PasswordPatch.Builder().
value(updated.getPassword()).
resources(updated.getResources()).build());
}
// 2. username
result.setUsername(
replacePatchItem(updated.getUsername(), original.getUsername(), new StringReplacePatchItem()));
// 3. security question / answer
if (updated.getSecurityQuestion() == null) {
result.setSecurityQuestion(null);
result.setSecurityAnswer(null);
} else if (!updated.getSecurityQuestion().equals(original.getSecurityQuestion())
|| StringUtils.isNotBlank(updated.getSecurityAnswer())) {
result.setSecurityQuestion(new StringReplacePatchItem.Builder().
value(updated.getSecurityQuestion()).build());
result.setSecurityAnswer(
new StringReplacePatchItem.Builder().value(updated.getSecurityAnswer()).build());
}
result.setMustChangePassword(replacePatchItem(
updated.isMustChangePassword(), original.isMustChangePassword(), new BooleanReplacePatchItem()));
// 4. roles
if (!incremental) {
original.getRoles().stream().filter(role -> !updated.getRoles().contains(role)).
forEach(toRemove -> {
result.getRoles().add(new StringPatchItem.Builder().
operation(PatchOperation.DELETE).value(toRemove).build());
});
}
updated.getRoles().stream().filter(role -> !original.getRoles().contains(role)).
forEach(toAdd -> {
result.getRoles().add(new StringPatchItem.Builder().
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()))).
forEachOrdered(entry -> {
result.getRelationships().add(new RelationshipPatch.Builder().
operation(PatchOperation.ADD_REPLACE).
relationshipTO(entry.getValue()).build());
});
if (!incremental) {
originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)).
forEach(key -> {
result.getRelationships().add(new RelationshipPatch.Builder().
operation(PatchOperation.DELETE).
relationshipTO(originalRels.get(key)).build());
});
}
// 6. memberships
Map updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships());
Map originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships());
updatedMembs.forEach((key, value) -> {
MembershipPatch membershipPatch = new MembershipPatch.Builder().
operation(PatchOperation.ADD_REPLACE).group(value.getGroupKey()).build();
diff(value, membershipPatch);
if (!originalMembs.containsKey(key)
|| (!membershipPatch.getPlainAttrs().isEmpty() || !membershipPatch.getVirAttrs().isEmpty())) {
result.getMemberships().add(membershipPatch);
}
});
if (!incremental) {
originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)).
forEach(key -> {
result.getMemberships().add(new MembershipPatch.Builder().
operation(PatchOperation.DELETE).group(originalMembs.get(key).getGroupKey()).build());
});
}
// 7. linked accounts
Map, LinkedAccountTO> updatedAccounts =
EntityTOUtils.buildLinkedAccountMap(updated.getLinkedAccounts());
Map, LinkedAccountTO> originalAccounts =
EntityTOUtils.buildLinkedAccountMap(original.getLinkedAccounts());
updatedAccounts.entrySet().stream().
forEachOrdered(entry -> {
result.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
operation(PatchOperation.ADD_REPLACE).
linkedAccountTO(entry.getValue()).build());
});
if (!incremental) {
originalAccounts.keySet().stream().filter(account -> !updatedAccounts.containsKey(account)).
forEach(key -> {
result.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
operation(PatchOperation.DELETE).
linkedAccountTO(originalAccounts.get(key)).build());
});
}
return result;
}
/**
* Calculate modifications needed by first in order to be equal to second.
*
* @param updated updated GroupTO
* @param original original GroupTO
* @param incremental perform incremental diff (without removing existing info)
* @return GroupPatch containing differences
*/
public static GroupPatch diff(final GroupTO updated, final GroupTO original, final boolean incremental) {
GroupPatch result = new GroupPatch();
diff(updated, original, result, incremental);
// 1. name
result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem()));
// 2. ownership
result.setUserOwner(
replacePatchItem(updated.getUserOwner(), original.getUserOwner(), new StringReplacePatchItem()));
result.setGroupOwner(
replacePatchItem(updated.getGroupOwner(), original.getGroupOwner(), new StringReplacePatchItem()));
// 3. dynamic membership
result.setUDynMembershipCond(updated.getUDynMembershipCond());
result.getADynMembershipConds().putAll(updated.getADynMembershipConds());
// 4. type extensions
result.getTypeExtensions().addAll(updated.getTypeExtensions());
return result;
}
@SuppressWarnings("unchecked")
public static P diff(
final TO updated, final TO original, final boolean incremental) {
if (updated instanceof UserTO && original instanceof UserTO) {
return (P) diff((UserTO) updated, (UserTO) original, incremental);
} else if (updated instanceof GroupTO && original instanceof GroupTO) {
return (P) diff((GroupTO) updated, (GroupTO) original, incremental);
} else if (updated instanceof AnyObjectTO && original instanceof AnyObjectTO) {
return (P) diff((AnyObjectTO) updated, (AnyObjectTO) original, incremental);
}
throw new IllegalArgumentException("Unsupported: " + updated.getClass().getName());
}
private static Collection patch(final Map attrs, final Set attrPatches) {
Map rwattrs = new HashMap<>(attrs);
attrPatches.forEach(patch -> {
if (patch.getAttrTO() == null) {
LOG.warn("Invalid {} specified: {}", AttrPatch.class.getName(), patch);
} else {
rwattrs.remove(patch.getAttrTO().getSchema());
if (patch.getOperation() == PatchOperation.ADD_REPLACE && !patch.getAttrTO().getValues().isEmpty()) {
rwattrs.put(patch.getAttrTO().getSchema(), patch.getAttrTO());
}
}
});
return rwattrs.values();
}
private static void patch(final T to, final K patch, final T result) {
// check same key
if (to.getKey() == null || !to.getKey().equals(patch.getKey())) {
throw new IllegalArgumentException(
to.getClass().getSimpleName() + " and "
+ patch.getClass().getSimpleName() + " keys must be the same");
}
// 0. realm
if (patch.getRealm() != null) {
result.setRealm(patch.getRealm().getValue());
}
// 1. auxiliary classes
for (StringPatchItem auxClassPatch : patch.getAuxClasses()) {
switch (auxClassPatch.getOperation()) {
case ADD_REPLACE:
result.getAuxClasses().add(auxClassPatch.getValue());
break;
case DELETE:
default:
result.getAuxClasses().remove(auxClassPatch.getValue());
}
}
// 2. plain attributes
result.getPlainAttrs().clear();
result.getPlainAttrs().addAll(patch(EntityTOUtils.buildAttrMap(to.getPlainAttrs()), patch.getPlainAttrs()));
// 3. virtual attributes
result.getVirAttrs().clear();
result.getVirAttrs().addAll(patch.getVirAttrs());
// 4. resources
for (StringPatchItem resourcePatch : patch.getResources()) {
switch (resourcePatch.getOperation()) {
case ADD_REPLACE:
result.getResources().add(resourcePatch.getValue());
break;
case DELETE:
default:
result.getResources().remove(resourcePatch.getValue());
}
}
}
public static AnyTO patch(final AnyTO anyTO, final AnyPatch anyPatch) {
if (anyTO instanceof UserTO) {
return patch((UserTO) anyTO, (UserPatch) anyPatch);
}
if (anyTO instanceof GroupTO) {
return patch((GroupTO) anyTO, (GroupPatch) anyPatch);
}
if (anyTO instanceof AnyObjectTO) {
return patch((AnyObjectTO) anyTO, (AnyObjectPatch) anyPatch);
}
return null;
}
public static GroupTO patch(final GroupTO groupTO, final GroupPatch groupPatch) {
GroupTO result = SerializationUtils.clone(groupTO);
patch(groupTO, groupPatch, result);
if (groupPatch.getName() != null) {
result.setName(groupPatch.getName().getValue());
}
if (groupPatch.getUserOwner() != null) {
result.setGroupOwner(groupPatch.getUserOwner().getValue());
}
if (groupPatch.getGroupOwner() != null) {
result.setGroupOwner(groupPatch.getGroupOwner().getValue());
}
result.setUDynMembershipCond(groupPatch.getUDynMembershipCond());
result.getADynMembershipConds().clear();
result.getADynMembershipConds().putAll(groupPatch.getADynMembershipConds());
return result;
}
public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectPatch anyObjectPatch) {
AnyObjectTO result = SerializationUtils.clone(anyObjectTO);
patch(anyObjectTO, anyObjectPatch, result);
if (anyObjectPatch.getName() != null) {
result.setName(anyObjectPatch.getName().getValue());
}
// 1. relationships
anyObjectPatch.getRelationships().
forEach(relPatch -> {
if (relPatch.getRelationshipTO() == null) {
LOG.warn("Invalid {} specified: {}", RelationshipPatch.class.getName(), relPatch);
} else {
result.getRelationships().remove(relPatch.getRelationshipTO());
if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
result.getRelationships().add(relPatch.getRelationshipTO());
}
}
});
// 2. memberships
anyObjectPatch.getMemberships().forEach(membPatch -> {
if (membPatch.getGroup() == null) {
LOG.warn("Invalid {} specified: {}", MembershipPatch.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().group(membPatch.getGroup()).build();
// 3. plain attributes
newMembershipTO.getPlainAttrs().addAll(membPatch.getPlainAttrs());
// 4. virtual attributes
newMembershipTO.getVirAttrs().addAll(membPatch.getVirAttrs());
result.getMemberships().add(newMembershipTO);
}
}
});
return result;
}
public static UserTO patch(final UserTO userTO, final UserPatch userPatch) {
UserTO result = SerializationUtils.clone(userTO);
patch(userTO, userPatch, result);
// 1. password
if (userPatch.getPassword() != null) {
result.setPassword(userPatch.getPassword().getValue());
}
// 2. username
if (userPatch.getUsername() != null) {
result.setUsername(userPatch.getUsername().getValue());
}
// 3. relationships
userPatch.getRelationships().forEach(relPatch -> {
if (relPatch.getRelationshipTO() == null) {
LOG.warn("Invalid {} specified: {}", RelationshipPatch.class.getName(), relPatch);
} else {
result.getRelationships().remove(relPatch.getRelationshipTO());
if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
result.getRelationships().add(relPatch.getRelationshipTO());
}
}
});
// 4. memberships
userPatch.getMemberships().forEach(membPatch -> {
if (membPatch.getGroup() == null) {
LOG.warn("Invalid {} specified: {}", MembershipPatch.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().group(membPatch.getGroup()).build();
// 3. plain attributes
newMembershipTO.getPlainAttrs().addAll(membPatch.getPlainAttrs());
// 4. virtual attributes
newMembershipTO.getVirAttrs().addAll(membPatch.getVirAttrs());
result.getMemberships().add(newMembershipTO);
}
}
});
// 5. roles
for (StringPatchItem rolePatch : userPatch.getRoles()) {
switch (rolePatch.getOperation()) {
case ADD_REPLACE:
result.getRoles().add(rolePatch.getValue());
break;
case DELETE:
default:
result.getRoles().remove(rolePatch.getValue());
}
}
// 6. linked accounts
userPatch.getLinkedAccounts().forEach(accountPatch -> {
if (accountPatch.getLinkedAccountTO() == null) {
LOG.warn("Invalid {} specified: {}", LinkedAccountPatch.class.getName(), accountPatch);
} else {
result.getLinkedAccounts().remove(accountPatch.getLinkedAccountTO());
if (accountPatch.getOperation() == PatchOperation.ADD_REPLACE) {
result.getLinkedAccounts().add(accountPatch.getLinkedAccountTO());
}
}
});
return result;
}
/**
* Add PLAIN attribute DELETE patch for those attributes of the input AnyTO without values or containing null value
*
* @param anyTO User, Group or Any Object to look for attributes with no value
* @param patch patch to enrich with DELETE statements
*/
public static void cleanEmptyAttrs(final AnyTO anyTO, final AnyPatch patch) {
patch.getPlainAttrs().addAll(anyTO.getPlainAttrs().stream().
filter(plainAttrTO -> isEmpty(plainAttrTO)).
map(plainAttrTO -> new AttrPatch.Builder().
operation(PatchOperation.DELETE).
attrTO(new AttrTO.Builder().schema(plainAttrTO.getSchema()).build()).
build()).collect(Collectors.toSet()));
}
private static boolean isEmpty(final AttrTO attrTO) {
return attrTO.getValues().isEmpty() || NULL_SINGLETON.equals(attrTO.getValues());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy