All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.syncope.common.lib.AnyOperations Maven / Gradle / Ivy

There is a newer version: 2.1.14
Show newest version
/*
 * 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.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.Closure;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
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.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.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 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 MembershipTO updated,
            final MembershipTO original,
            final MembershipPatch result,
            final boolean incremental) {

        // check same key
        if (updated.getGroupKey() == null && original.getGroupKey() != null
                || (updated.getGroupKey() != null && !updated.getGroupKey().equals(original.getGroupKey()))) {

            throw new IllegalArgumentException("Memberships must be the same");
        }
        result.setGroup(updated.getGroupKey());

        // 1. plain attributes
        Map updatedAttrs = new HashMap<>(updated.getPlainAttrMap());
        Map originalAttrs = new HashMap<>(original.getPlainAttrMap());

        result.getPlainAttrs().clear();

        if (!incremental) {
            IterableUtils.forEach(CollectionUtils.subtract(originalAttrs.keySet(), updatedAttrs.keySet()),
                    new Closure() {

                @Override
                public void execute(final String schema) {
                    result.getPlainAttrs().add(new AttrPatch.Builder().
                            operation(PatchOperation.DELETE).
                            attrTO(new AttrTO.Builder().schema(schema).build()).
                            build());
                }
            });
        }

        for (AttrTO attrTO : updatedAttrs.values()) {
            if (attrTO.getValues().isEmpty()) {
                if (!incremental) {
                    result.getPlainAttrs().add(new AttrPatch.Builder().
                            operation(PatchOperation.DELETE).
                            attrTO(new AttrTO.Builder().schema(attrTO.getSchema()).build()).
                            build());
                }
            } else {
                AttrPatch patch = new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).attrTO(attrTO).build();
                if (!patch.isEmpty()) {
                    result.getPlainAttrs().add(patch);
                }
            }
        }

        // 2. virtual attributes
        result.getVirAttrs().clear();
        result.getVirAttrs().addAll(updated.getVirAttrs());
    }

    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) {
            for (String auxClass : CollectionUtils.subtract(original.getAuxClasses(), updated.getAuxClasses())) {
                result.getAuxClasses().add(
                        new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(auxClass).build());
            }
        }

        for (String auxClass : CollectionUtils.subtract(updated.getAuxClasses(), original.getAuxClasses())) {
            result.getAuxClasses().add(
                    new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(auxClass).build());
        }

        // 3. plain attributes
        Map updatedAttrs = new HashMap<>(updated.getPlainAttrMap());
        Map originalAttrs = new HashMap<>(original.getPlainAttrMap());

        result.getPlainAttrs().clear();

        if (!incremental) {
            IterableUtils.forEach(CollectionUtils.subtract(originalAttrs.keySet(), updatedAttrs.keySet()),
                    new Closure() {

                @Override
                public void execute(final String schema) {
                    result.getPlainAttrs().add(new AttrPatch.Builder().
                            operation(PatchOperation.DELETE).
                            attrTO(new AttrTO.Builder().schema(schema).build()).
                            build());
                }
            });
        }

        for (AttrTO attrTO : updatedAttrs.values()) {
            if (attrTO.getValues().isEmpty()) {
                if (!incremental) {
                    result.getPlainAttrs().add(new AttrPatch.Builder().
                            operation(PatchOperation.DELETE).
                            attrTO(new AttrTO.Builder().schema(attrTO.getSchema()).build()).
                            build());
                }
            } else {
                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) {
            for (String resource : CollectionUtils.subtract(original.getResources(), updated.getResources())) {
                result.getResources().add(
                        new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(resource).build());
            }
        }

        for (String resource : CollectionUtils.subtract(updated.getResources(), original.getResources())) {
            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 = updated.getRelationshipMap();
        Map, RelationshipTO> originalRels = original.getRelationshipMap();

        for (Map.Entry, RelationshipTO> entry : updatedRels.entrySet()) {
            if (!originalRels.containsKey(entry.getKey())) {
                result.getRelationships().add(new RelationshipPatch.Builder().
                        operation(PatchOperation.ADD_REPLACE).
                        relationshipTO(entry.getValue()).build());
            }
        }

        if (!incremental) {
            for (Pair key : CollectionUtils.subtract(originalRels.keySet(), updatedRels.keySet())) {
                result.getRelationships().add(new RelationshipPatch.Builder().
                        operation(PatchOperation.DELETE).
                        relationshipTO(originalRels.get(key)).build());
            }
        }

        // 3. memberships
        Map updatedMembs = updated.getMembershipMap();
        Map originalMembs = original.getMembershipMap();

        for (Map.Entry entry : updatedMembs.entrySet()) {
            if (!originalMembs.containsKey(entry.getKey())) {
                result.getMemberships().add(new MembershipPatch.Builder().
                        operation(PatchOperation.ADD_REPLACE).group(entry.getValue().getGroupKey()).build());
            }
        }

        if (!incremental) {
            for (String key : CollectionUtils.subtract(originalMembs.keySet(), updatedMembs.keySet())) {
                result.getMemberships().add(new MembershipPatch.Builder().
                        operation(PatchOperation.DELETE).group(originalMembs.get(key).getGroupKey()).build());
            }
        }

        return result;
    }

    /**
     * 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()).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) {
            for (String toRemove : CollectionUtils.subtract(original.getRoles(), updated.getRoles())) {
                result.getRoles().add(
                        new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(toRemove).build());
            }
        }

        for (String toAdd : CollectionUtils.subtract(updated.getRoles(), original.getRoles())) {
            result.getRoles().add(
                    new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(toAdd).build());
        }

        // 5. relationships
        Map, RelationshipTO> updatedRels = updated.getRelationshipMap();
        Map, RelationshipTO> originalRels = original.getRelationshipMap();

        for (Map.Entry, RelationshipTO> entry : updatedRels.entrySet()) {
            if (!originalRels.containsKey(entry.getKey())) {
                result.getRelationships().add(new RelationshipPatch.Builder().
                        operation(PatchOperation.ADD_REPLACE).
                        relationshipTO(entry.getValue()).build());
            }
        }

        if (!incremental) {
            for (Pair key : CollectionUtils.subtract(originalRels.keySet(), updatedRels.keySet())) {
                result.getRelationships().add(new RelationshipPatch.Builder().
                        operation(PatchOperation.DELETE).
                        relationshipTO(originalRels.get(key)).build());
            }
        }

        // 6. memberships
        Map updatedMembs = updated.getMembershipMap();
        Map originalMembs = original.getMembershipMap();

        for (Map.Entry entry : updatedMembs.entrySet()) {
            MembershipPatch membershipPatch = new MembershipPatch.Builder().
                    operation(PatchOperation.ADD_REPLACE).group(entry.getValue().getGroupKey()).build();

            MembershipTO omemb;
            if (originalMembs.containsKey(entry.getKey())) {
                // get the original membership
                omemb = originalMembs.get(entry.getKey());
            } else {
                // create an empty one to generate the patch
                omemb = new MembershipTO();
                omemb.setGroupKey(entry.getKey());
            }

            diff(entry.getValue(), omemb, membershipPatch, incremental);
            result.getMemberships().add(membershipPatch);
        }

        if (!incremental) {
            for (String key : CollectionUtils.subtract(originalMembs.keySet(), updatedMembs.keySet())) {
                result.getMemberships().add(new MembershipPatch.Builder().
                        operation(PatchOperation.DELETE).group(originalMembs.get(key).getGroupKey()).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);
        for (AttrPatch patch : attrPatches) {
            if (patch.getAttrTO() == null) {
                LOG.warn("Invalid {} specified: {}", AttrPatch.class.getName(), patch);
            } else {
                rwattrs.remove(patch.getAttrTO().getSchema());
                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
                    rwattrs.put(patch.getAttrTO().getSchema(), patch.getAttrTO());
                }
                switch (patch.getOperation()) {
                    case ADD_REPLACE:
                        if (rwattrs.containsKey(patch.getAttrTO().getSchema())) {
                            rwattrs.remove(patch.getAttrTO().getSchema());
                        }
                        break;

                    case DELETE:
                    default:
                        rwattrs.remove(patch.getAttrTO().getSchema());
                }
            }
        }

        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:
                    to.getAuxClasses().add(auxClassPatch.getValue());
                    break;

                case DELETE:
                default:
                    to.getAuxClasses().remove(auxClassPatch.getValue());
            }
        }

        // 2. plain attributes
        result.getPlainAttrs().clear();
        result.getPlainAttrs().addAll(patch(to.getPlainAttrMap(), 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 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);

        // 1. relationships
        for (RelationshipPatch relPatch : anyObjectPatch.getRelationships()) {
            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
        for (final MembershipPatch membPatch : anyObjectPatch.getMemberships()) {
            if (membPatch.getGroup() == null) {
                LOG.warn("Invalid {} specified: {}", MembershipPatch.class.getName(), membPatch);
            } else {
                MembershipTO memb = IterableUtils.find(result.getMemberships(), new Predicate() {

                    @Override
                    public boolean evaluate(final MembershipTO object) {
                        return membPatch.getGroup().equals(object.getGroupKey());
                    }
                });
                if (memb != null) {
                    result.getMemberships().remove(memb);
                }

                if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
                    MembershipTO newMembershipTO = new MembershipTO();
                    newMembershipTO.setGroupKey(membPatch.getGroup());

                    if (memb == null) {
                        for (AttrPatch attrPatch : membPatch.getPlainAttrs()) {
                            newMembershipTO.getPlainAttrs().add(attrPatch.getAttrTO());
                        }
                    } else {
                        newMembershipTO.getPlainAttrs().addAll(
                                patch(memb.getPlainAttrMap(), membPatch.getPlainAttrs()));
                    }

                    // 3. 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
        for (RelationshipPatch relPatch : userPatch.getRelationships()) {
            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
        for (final MembershipPatch membPatch : userPatch.getMemberships()) {
            if (membPatch.getGroup() == null) {
                LOG.warn("Invalid {} specified: {}", MembershipPatch.class.getName(), membPatch);
            } else {
                MembershipTO memb = IterableUtils.find(result.getMemberships(), new Predicate() {

                    @Override
                    public boolean evaluate(final MembershipTO object) {
                        return membPatch.getGroup().equals(object.getGroupKey());
                    }
                });
                if (memb != null) {
                    result.getMemberships().remove(memb);
                }

                if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
                    MembershipTO newMembershipTO = new MembershipTO();
                    newMembershipTO.setGroupKey(membPatch.getGroup());

                    if (memb == null) {
                        for (AttrPatch attrPatch : membPatch.getPlainAttrs()) {
                            newMembershipTO.getPlainAttrs().add(attrPatch.getAttrTO());
                        }
                    } else {
                        newMembershipTO.getPlainAttrs().addAll(
                                patch(memb.getPlainAttrMap(), membPatch.getPlainAttrs()));
                    }

                    // 3. 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());
            }
        }

        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy