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

com.memority.citadel.shared.api.im.ApiObject Maven / Gradle / Ivy

Go to download

This artifact provides the API classes that are necessary to implement general configuration Rules on the Memority IM platform.

There is a newer version: 3.43.1
Show newest version
/*
 * Copyright (c) 2016-2023 Memority. All Rights Reserved.
 *
 * This file is part of Memority Citadel API , a Memority project.
 *
 * This file is released under the Memority Public Artifacts End-User License Agreement,
 * see 
 * Unauthorized copying of this file, via any medium is strictly prohibited.
 */
package com.memority.citadel.shared.api.im;

import org.apache.commons.lang3.Validate;

import com.memority.toolkit.core.api.groovy.ReadWriteMapLikeGroovyObject;
import com.memority.toolkit.core.api.changetracking.ChangeTracking;
import com.memority.toolkit.core.api.changetracking.ChangeTrackingList;
import com.memority.toolkit.core.api.changetracking.ChangeTrackingMap;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;

/**
 * Managed object representation suitable for Groovy scripts (rules, actions,...).
 * All managed objects exposed in Groovy scripts are represented as ApiObjects.
 */
public class ApiObject implements ReadWriteMapLikeGroovyObject, ChangeTracking, HasMutableValues {

    private final String KEY_KIND = BuiltinAttributeIds.KIND;
    private final String KEY_TYPE = BuiltinAttributeIds.TYPE;
    private final String KEY_ID = BuiltinAttributeIds.ID;

    private final ChangeTrackingMap map;

    public ApiObject() {
        this.map = new ChangeTrackingMap<>();
    }
    public ApiObject(List> attributeValues) {
        this.map = new ChangeTrackingMap<>();
        this.init(attributeValues);
    }
    private ApiObject(ChangeTrackingMap map) {
        this.map = map;
    }

    @Override
    public boolean hasChanged() {
        return Stream.concat(Stream.of(this.map), this.map.values().stream())
                .filter(o -> o instanceof ChangeTracking)
                .anyMatch(o -> ((ChangeTracking) o).hasChanged());
    }

    @Override
    public void resetChanged() {
        Stream.concat(Stream.of(this.map), Stream.of(this.map.values()))
                .filter(o -> o instanceof ChangeTracking)
                .forEach(o -> ((ChangeTracking) o).resetChanged());
    }

    public ApiObject(ObjectKind objectKind, String objectType, List> attributeValues) {
        this(attributeValues);
        this.setKind(objectKind);
        this.setType(objectType);
    }

    public ApiObject(ObjectKind objectKind, String objectType) {
        this(objectKind, objectType, emptyList());
    }

    private void init(List> attributeValues) {
        for (AttributeValue attributeValue:attributeValues) {
            if (attributeValue.isMultiValued()) {
                this.map.put(attributeValue.getId(), new AttributeChangeTrackingList(attributeValue.getValues(), attributeValue.getOrigin()));
            } else {
                if (attributeValue.hasValue()) {
                    this.map.put(attributeValue.getId(), new AttributeChangeTrackingValue(attributeValue.getValue(), attributeValue.getOrigin()));
                } else {
                    this.map.put(attributeValue.getId(), new AttributeChangeTrackingValue(null, attributeValue.getOrigin()));
                }
            }
        }
        this.resetChanged();
    }

    public List> attributeValues() {
        return this.map.entrySet().stream()
                .map(e -> this.attributeValue(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
    }

    public AttributeValue attributeValue(String id) {
        return attributeValue(id, this.map.get(id));
    }

    public Map> attributeValueMap() {
        return map.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> this.attributeValue(e.getKey(), e.getValue())));
    }

    private AttributeValue attributeValue(String id, AttributeWrapper wrapper) {
        if (wrapper == null) {
            return AttributeValue.from(id, null);
        }
        AttributeValue val = AttributeValue.from(id, wrapper.unwrap());
        val.setOrigin(wrapper.getAttributeOrigin());
        return val;
    }

    public ObjectKind getKind() {
        return (ObjectKind) getProperty(KEY_KIND);
    }

    public ApiObject setKind(ObjectKind kind) {
        this.map.put(KEY_KIND, new AttributeChangeTrackingValue(kind, AttributeOrigin.SYSTEM));
        return this;
    }

    public String getId() {
        //noinspection RedundantClassCall
        return String.class.cast(getProperty(KEY_ID));
    }

    public ApiObject setId(String id) {
        this.map.put(KEY_ID, new AttributeChangeTrackingValue(id, AttributeOrigin.SYSTEM));
        return this;
    }

    public String getType() {
        //noinspection RedundantClassCall
        return String.class.cast(getProperty(KEY_TYPE));
    }

    public ApiObject setType(String type) {
        this.map.put(KEY_TYPE, new AttributeChangeTrackingValue(type, AttributeOrigin.SYSTEM));
        return this;
    }

    @Override
    public Object getProperty(String propertyName) {
        AttributeWrapper wrapped = this.map.get(propertyName);
        return wrapped == null ? null : wrapped.unwrap();
    }

    @Override
    public void setProperty(String propertyName, Object newValue) {
        if (newValue instanceof Iterable) {
            AttributeChangeTrackingList values = new AttributeChangeTrackingList(AttributeOrigin.RULE);
            ((Iterable) newValue).forEach(values::add); // will mark list as changed
            this.map.put(propertyName, values);
        } else {
            this.map.put(propertyName, new AttributeChangeTrackingValue(newValue, AttributeOrigin.RULE));
        }
    }

    /**
     * Deprecated since {@link ApiObject} does not implement {@code Map} anymore,
     * kept for backward compatibility.
     *
     * @param propertyName the attribute id
     * @return the attribute value
     */
    @Deprecated
    public Object get(String propertyName) {
        return getProperty(propertyName);
    }

    /**
     * Deprecated since {@link ApiObject} does not implement {@code Map} anymore,
     * kept for backward compatibility.
     *
     * @param propertyName the id of the attribute to set
     * @param newValue the value to set
     * @return the old attribute value
     */
    @Deprecated
    public Object put(String propertyName, Object newValue) {
        Object oldValue = getProperty(propertyName);
        setProperty(propertyName, newValue);
        return oldValue;
    }

    @Override
    public ApiObject immutable() {
        return new ApiObject(this.map.immutable());
    }

    @Override
    public List values(String attributeId) {
        if (! this.has(attributeId)) {
            // No property by that name, initialize an empty list
            setProperty(attributeId, Collections.emptyList());
        }

        Object value = getProperty(attributeId);
        if (value instanceof List) {
            return (List) value;
        } else {
            // As per the HasValue interface, we should return a list anyway. However, this
            // should not be expected to be mutable to change the attribute value
            // so we return an unmodifiable list.
            return Collections.singletonList(value);
        }
    }

    @Override
    public Object value(String attributeId) {
        Object value = getProperty(attributeId);
        if (value == null) {
            return null;
        } else if (value instanceof List) {
            return ((List) value).isEmpty() ? null : ((List) value).get(0);
        } else {
            return value;
        }
    }

    @Override
    public boolean hasValue(String attributeId) {
        return ! values(attributeId).isEmpty();
    }

    @Override
    public boolean has(String attributeId) {
        return this.map.containsKey(attributeId);
    }

    @Override
    public Set names() {
        return this.map.keySet();
    }

    @Override
    public  void setValue(String name, T newValue) {
        setProperty(name, newValue);
    }

    @Override
    public  void setValues(String name, List newValues) {
        Validate.notNull(newValues);
        setProperty(name, newValues);
    }

    @Override
    public  boolean addValue(String name, T additionalValue, boolean distinct, boolean sorted) {
        return addValues(name, Collections.singletonList(additionalValue), distinct, sorted);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  boolean addValues(String name, List additionalValues, boolean distinct, boolean sorted) {
        List values = (List) values(name);
        boolean changed = false;
        for (T additionalValue:additionalValues) {
            if (distinct && values.contains(additionalValue)) {
                continue;
            }
            changed |= values.add(additionalValue);
        }
        if (sorted) {
            values.sort((Comparator)Comparator.naturalOrder());
            changed = true;
        }
        return changed;
    }

    @Override
    public  boolean removeValue(String name, T valueToRemove) {
        return removeValues(name, Collections.singletonList(valueToRemove));
    }

    @Override
    public  boolean removeValues(String name, List valuesToRemove) {
        Object val = getProperty(name);
        if (val == null) {
            return false;
        } else if (val instanceof AttributeChangeTrackingList) {
            return ((AttributeChangeTrackingList) val).removeAll(valuesToRemove);
        } else {
            if (valuesToRemove.contains(val)) {
                setProperty(name, null);
                return true;
            } else {
                return false;
            }
        }
    }

    @Override
    public boolean remove(String name) {
        // If no value, nothing to do
        if (!this.map.containsKey(name)) {
            return false;
        }

        // Get value and...
        Object value = this.map.get(name);

        // Value is null, won't do much with that
        if (value == null) {
            return false;
        }

        if (value instanceof List) {
            @SuppressWarnings("rawtypes")
            List list = (List) value;
            boolean changed = !list.isEmpty();
            list.clear();
            return changed;
        } else {
            this.map.put(name, null);
            return true;
        }
    }

    @Override
    public boolean delete(String name) {
        return this.map.remove(name) != null;
    }

    interface AttributeWrapper {
        AttributeOrigin getAttributeOrigin();
        Object unwrap();
    }

    static class AttributeChangeTrackingList extends ChangeTrackingList implements AttributeWrapper {
        private AttributeOrigin origin;

        public AttributeChangeTrackingList(List values, AttributeOrigin origin) {
            this.origin = origin;
            this.addAll(values);
            this.resetChanged();
        }
        public AttributeChangeTrackingList(AttributeOrigin origin) {
            this.origin = origin;
        }
        protected AttributeChangeTrackingList(List delegate) {
            super(delegate);
        }
        @Override
        public AttributeOrigin getAttributeOrigin() {
            return hasChanged() ? AttributeOrigin.RULE : origin;
        }

        @Override
        public Object unwrap() {
            return this;
        }

        @Override
        public ChangeTrackingList immutable() {
            AttributeChangeTrackingList immutable = new AttributeChangeTrackingList(Collections.unmodifiableList(this.delegate));
            immutable.origin = this.origin;
            return immutable;
        }
    }

    static class AttributeChangeTrackingValue implements AttributeWrapper {
        private final AttributeOrigin origin;
        private final Object value;

        public AttributeChangeTrackingValue(Object value, AttributeOrigin origin) {
            this.origin = origin;
            this.value = value;
        }

        @Override
        public AttributeOrigin getAttributeOrigin() {
            return this.origin;
        }

        @Override
        public Object unwrap() {
            return this.value;
        }
    }
}