HookServices.groovy 6.65 KB
import javax.transaction.Synchronization
import javax.transaction.xa.XAResource
import javax.transaction.xa.Xid

import groovy.transform.Field

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import org.moqui.util.RestClient
import org.moqui.util.ObjectUtilities
import org.moqui.context.ExecutionContext
import org.moqui.entity.EntityCondition
import org.moqui.entity.EntityFind
import org.moqui.entity.EntityList
import org.moqui.entity.EntityValue

@Field Logger logger = LoggerFactory.getLogger(getClass().getName())
//'HookServices')

class HookSynchronization implements Synchronization {
    private final ExecutionContext ec;

    private static class EntityToken {
        protected final String entityName
        protected final String keyName
        protected final Object keyValue
        protected final Map<String, Object> extraParameters = new HashMap<String, Object>()

        protected EntityToken(String entityName, String keyName, Object keyValue) {
            this.entityName = entityName
            this.keyName = keyName
            this.keyValue = keyValue
        }

        public boolean equals(Object other) {
            if (!(other instanceof EntityToken)) {
                return false
            }
            EntityToken that = (EntityToken) other
            return entityName.equals(that.entityName) && keyName.equals(that.keyName) && keyValue.equals(that.keyValue)
        }

        public int hashCode() {
            return entityName.hashCode() ^ keyName.hashCode() ^ keyValue.hashCode()
        }
    }

    private Map<EntityToken, Map<String, Object>> updates = new LinkedHashMap<>()

    protected HookSynchronization(ExecutionContext ec) {
        this.ec = ec
    }

    protected void add(String entityName, String keyName, Object keyValue, Map<String, Object> extraParameters) {
        EntityToken entityToken = new EntityToken(entityName, keyName, keyValue)
        // This always move the value to the end of the list
        Map<String, Object> existingExtraParameters = updates.remove(entityToken)
        Map<String, Object> newExtraParameters = [:]
        if (existingExtraParameters) {
            newExtraParameters.putAll(existingExtraParameters)
        }
        if (extraParameters) {
            newExtraParameters.putAll(extraParameters)
        }
        updates.put(entityToken, newExtraParameters)
        if (existingExtraParameters == null) {
            ec.getLogger().info("Add new synchronization for ${entityName}: [${keyValue}]")
        } else {
            ec.getLogger().info("Updated synchronization for ${entityName}: [${keyValue}]")
        }
    }

    @Override
    public void beforeCompletion() {
        List<Map<String, Object>> updates = []
        for (Map.Entry<EntityToken, Map<String, Object>> entry: this.updates.entrySet()) {
            EntityToken token = entry.getKey()
            Map<String, Object> extraParameters = entry.getValue()
            updates.add([
                entityName: token.entityName,
                keyName: token.keyName,
                keyValue: token.keyValue,
                extraParameters: extraParameters,
            ])
        }
        if (updates) {
            ec.getService().async().name("keycloak.HookServices.process#Updates").parameter('updates', updates).call()
        }
    }

    @Override
    public void afterCompletion(int status) {

    }
}

HookSynchronization getHookSync() {
    HookSynchronization hookSync = ec.transaction.getActiveSynchronization(getClass().getName())
    if (hookSync == null) {
        hookSync = new HookSynchronization(ec)
        ec.transaction.putAndEnlistActiveSynchronization(getClass().getName(), hookSync)
    }
    return hookSync
}

Map<String, Object> handleEntityUpdate() {
    logger.info("handleEntityUpdate: ${context.entityName}[${context.value}]")
    logger.info("context: ${context}")
    ExecutionContext ec = context.ec
    Map<String, Object> contextRoot = ec.getContextRoot()

    HookSynchronization hookSync = getHookSync()

    List<Map<String, Object>> queue = [[entityName: context.entityName, value: context.value]]

    while (!queue.isEmpty()) {
        Map<String, Object> entry = queue.remove(0)
        logger.info("processing entry: ${entry}")
        String entityName = entry.entityName
        Object value = entry.value
        String keyName
        switch (entityName) {
            case 'Party':
                EntityList userAccounts = ec.entity.find('UserAccount').condition('partyId', value).list()
                for (EntityValue userAccount: userAccounts) {
                    queue.add([entityName: 'UserAccount', value: userAccount.userId])
                }
                continue
            case 'ContactMech':
                EntityList partyContactMechs = ec.entity.find('PartyContactMech').condition('contactMechId', value).list()
                for (EntityValue partyContactMech: partyContactMechs) {
                    queue.add([entityName: 'Party', value: partyContactMech.partyId])
                }
                continue
            case 'UserAccount':
                keyName = 'userId'
                break
            case 'UserGroup':
                keyName = 'userGroupId'
                break
            case 'UserGroupMember':
                keyName = 'userGroupId'
                break
            case 'UserPermission':
                keyName = 'userPermissionId'
                break
            case 'PartyClassification':
                keyName = 'partyClassificationId'
                break
            case 'RoleType':
                keyName = 'roleTypeId'
                break
        }
        hookSync.add(entityName, keyName, value, [:])
        //ec.getService().special().name("keycloak.KeycloakServices.send#${entityName}").parameter(keyName, value).registerOnCommit()
    }
}

Map<String, Object> handleUpdatePasswordInternal() {
    String userId = ec.context.userId
    String newPassword = ec.context.newPassword
    Boolean requirePasswordChange = ec.context.requirePasswordChange
    ec.getLogger().info("Registered synchronization for update#PasswordInternal: [${userId}]")
    HookSynchronization hookSync = getHookSync()
    hookSync.add('UserAccount', 'userId', userId, [
        newPassword: newPassword,
        requirePasswordChange: requirePasswordChange,
    ])
}

Map<String, Object> processUpdates() {
    List<Map<String, Object>> updates = context.updates

    for (Map<String, Object> update: updates) {
        Map<String, Object> parameters = [:]
        parameters[update.keyName] = update.keyValue
        parameters.putAll(update.extraParameters)
        ec.getService().sync().name("keycloak.KeycloakServices.send#${update.entityName}").parameters(parameters).call()
    }
    return [:]
}