io.lavagna.service.ApiHooksService.java Source code

Java tutorial

Introduction

Here is the source code for io.lavagna.service.ApiHooksService.java

Source

/**
 * This file is part of lavagna.
 *
 * lavagna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * lavagna is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with lavagna.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.lavagna.service;

import io.lavagna.common.Json;
import io.lavagna.model.*;
import io.lavagna.model.CardLabelValue.LabelValue;
import io.lavagna.model.apihook.Column;
import io.lavagna.model.apihook.From;
import io.lavagna.model.apihook.Label;
import io.lavagna.query.ApiHookQuery;
import io.lavagna.service.EventEmitter.LavagnaEvent;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.script.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Service
public class ApiHooksService {

    private static final Logger LOG = LogManager.getLogger();

    private final Compilable engine;
    private final Executor executor;
    private final Map<String, Triple<ApiHook, Map<String, String>, CompiledScript>> compiledScriptCache = new ConcurrentHashMap<>();
    private final ProjectService projectService;
    private final CardService cardService;
    private final ApiHookQuery apiHookQuery;
    private final LabelService labelService;
    private final UserService userService;
    private final ConfigurationRepository configurationRepository;

    public ApiHooksService(ProjectService projectService, CardService cardService, ApiHookQuery apiHookQuery,
            LabelService labelService, UserService userService, ConfigurationRepository configurationRepository) {
        this.projectService = projectService;
        this.cardService = cardService;
        this.apiHookQuery = apiHookQuery;
        this.labelService = labelService;
        this.userService = userService;
        this.configurationRepository = configurationRepository;
        engine = (Compilable) new ScriptEngineManager().getEngineByName("javascript");
        executor = Executors.newFixedThreadPool(4);
    }

    private static void executeScript(String name, CompiledScript script, Map<String, Object> scope) {
        try {
            ScriptContext newContext = new SimpleScriptContext();
            Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
            engineScope.putAll(scope);
            engineScope.put("log", LOG);
            engineScope.put("GSON", Json.GSON);
            engineScope.put("restTemplate", new RestTemplate());
            script.eval(newContext);
        } catch (ScriptException ex) {
            LOG.warn("Error while executing script " + name, ex);
        }
    }

    public ApiHook findByName(String name) {
        return apiHookQuery.findByNames(Collections.singletonList(name)).get(0);
    }

    private static class EventToRun implements Runnable {

        private final ApiHooksService apiHooksService;
        private final LavagnaEvent eventName;
        private final String projectName;
        private final Map<String, Object> env;
        private final io.lavagna.model.apihook.User user;

        EventToRun(ApiHooksService apiHooksService, LavagnaEvent eventName, String projectName, User user,
                Map<String, Object> env) {
            this.apiHooksService = apiHooksService;
            this.eventName = eventName;
            this.projectName = projectName;
            this.env = env;
            this.user = From.from(user);
        }

        @Override
        public void run() {

            List<ApiHookNameAndVersion> nameAndVersions = apiHooksService.apiHookQuery
                    .findAllEnabled(ApiHook.Type.EVENT_EMITTER_HOOK);
            List<String> names = new ArrayList<>(nameAndVersions.size());
            for (ApiHookNameAndVersion nv : nameAndVersions) {
                names.add(nv.getName());
            }

            //remove all disabled scripts
            apiHooksService.compiledScriptCache.keySet().retainAll(names);

            List<String> toAddOrUpdate = new ArrayList<>(0);
            for (ApiHookNameAndVersion hook : nameAndVersions) {
                if (!apiHooksService.compiledScriptCache.containsKey(hook.getName())
                        || apiHooksService.compiledScriptCache.get(hook.getName()).getLeft().getVersion() < hook
                                .getVersion()) {
                    toAddOrUpdate.add(hook.getName());
                }
            }

            if (!toAddOrUpdate.isEmpty()) {
                for (ApiHook apiHook : apiHooksService.apiHookQuery.findByNames(toAddOrUpdate)) {
                    try {
                        CompiledScript cs = apiHooksService.engine.compile(apiHook.getScript());
                        Map<String, String> configuration = apiHook.getConfiguration() != null
                                ? apiHook.getConfiguration()
                                : Collections.<String, String>emptyMap();
                        apiHooksService.compiledScriptCache.put(apiHook.getName(),
                                Triple.of(apiHook, configuration, cs));
                    } catch (ScriptException ex) {
                        LOG.warn("Error while compiling script " + apiHook.getName(), ex);
                    }
                }
            }

            for (Triple<ApiHook, Map<String, String>, CompiledScript> val : apiHooksService.compiledScriptCache
                    .values()) {
                List<String> projectsFilter = val.getLeft().getProjects();
                if (projectsFilter != null && !projectsFilter.contains(projectName)) {
                    continue;
                }

                Map<String, Object> scope = new HashMap<>(env);

                scope.put("eventName", eventName.name());
                scope.put("project", projectName);
                scope.put("user", user);
                scope.put("data", env);
                scope.put("configuration", val.getMiddle());

                executeScript(val.getLeft().getName(), val.getRight(), scope);
            }

        }
    }

    @Transactional(readOnly = true)
    public List<ApiHook> findAllPlugins() {
        return apiHookQuery.findAll();
    }

    @Transactional
    public void deleteHook(String name) {
        apiHookQuery.delete(name);
    }

    @Transactional
    public void enable(String name, boolean enabled) {
        apiHookQuery.enable(name, enabled);
    }

    @Transactional
    public void createApiHook(String name, String code, Map<String, String> properties, List<String> projects,
            Map<String, Object> metadata) {
        String propAsJson = properties == null ? null : Json.GSON.toJson(properties, Map.class);
        String projectsAsJson = projects == null ? null : Json.GSON.toJson(projects, List.class);
        String metadataAsJson = metadata == null ? null : Json.GSON.toJson(metadata, Map.class);
        apiHookQuery.insert(name, code, propAsJson, true, ApiHook.Type.EVENT_EMITTER_HOOK, projectsAsJson,
                metadataAsJson);
    }

    @Transactional
    public void updateApiHook(String name, String code, Map<String, String> properties, List<String> projects) {
        String propAsJson = properties == null ? null : Json.GSON.toJson(properties, Map.class);
        String projectsAsJson = projects == null ? null : Json.GSON.toJson(projects, List.class);
        apiHookQuery.update(name, code, propAsJson, apiHookQuery.findStatusByName(name),
                ApiHook.Type.EVENT_EMITTER_HOOK, projectsAsJson);
    }

    private Map<String, Object> getBaseDataFor(int cardId) {
        CardFull cf = cardService.findFullBy(cardId);
        String baseUrl = configurationRepository.getValue(Key.BASE_APPLICATION_URL);
        return getBaseDataFor(cf, baseUrl);
    }

    private static Map<String, Object> getBaseDataFor(CardFull cf, String baseUrl) {
        Map<String, Object> res = new HashMap<>();
        res.put("card", From.from(cf, baseUrl));
        res.put("board", cf.getBoardShortName());
        return res;
    }

    private Map<String, Object> updateForObj(int cardId, Object previous, Object updated) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("previous", previous);
        payload.put("updated", updated);
        payload.putAll(getBaseDataFor(cardId));
        return payload;
    }

    private Map<String, Object> updateFor(int cardId, String previous, String updated) {
        return updateForObj(cardId, previous, updated);
    }

    private Map<String, Object> updateFor(int cardId, CardData previous, String updated) {
        return updateForObj(cardId, From.from(previous), updated);
    }

    private Map<String, Object> updateFor(int cardId, CardType type, CardDataHistory previous,
            CardDataHistory updated) {
        return updateForObj(cardId, From.from(type, previous), From.from(type, updated));
    }

    private Map<String, Object> payloadForObj(int cardId, String name, Object object) {
        Map<String, Object> r = getBaseDataFor(cardId);
        r.put(name, object);
        return r;
    }

    private Map<String, Object> payloadFor(int cardId, String name, CardData cardData) {
        return payloadForObj(cardId, name, From.from(cardData));
    }

    private Map<String, Object> payloadFor(int cardId, String name, String value) {
        return payloadForObj(cardId, name, value);
    }

    private Map<String, Object> payloadFor(int cardId, String name, Collection<?> value) {
        return payloadForObj(cardId, name, value);
    }

    public void createdProject(String projectShortName, User user, LavagnaEvent event) {
        executor.execute(
                new EventToRun(this, event, projectShortName, user, Collections.<String, Object>emptyMap()));
    }

    public void updatedProject(String projectShortName, User user, LavagnaEvent event) {
        executor.execute(
                new EventToRun(this, event, projectShortName, user, Collections.<String, Object>emptyMap()));
    }

    public void createdBoard(String boardShortName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                Collections.<String, Object>singletonMap("board", boardShortName)));
    }

    public void updatedBoard(String boardShortName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                Collections.<String, Object>singletonMap("board", boardShortName)));
    }

    public void createdColumn(String boardShortName, String columnName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        Map<String, Object> payload = new HashMap<>();
        payload.put("board", boardShortName);
        payload.put("columnName", columnName);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void updateColumn(String boardShortName, BoardColumn oldColumn, BoardColumn updatedColumn, User user,
            LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        Map<String, Object> payload = new HashMap<>();
        payload.put("board", boardShortName);
        payload.put("previous", From.from(oldColumn));
        payload.put("updated", From.from(updatedColumn));
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void createdCard(String boardShortName, Card card, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        executor.execute(new EventToRun(this, event, projectShortName, user, getBaseDataFor(card.getId())));
    }

    public void updatedCardName(String boardShortName, Card beforeUpdate, Card newCard, User user,
            LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByBoardShortname(boardShortName);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                updateForObj(beforeUpdate.getId(), beforeUpdate.getName(), newCard.getName())));
    }

    public void updateCardDescription(int cardId, CardDataHistory previousDescription,
            CardDataHistory newDescription, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                updateFor(cardId, CardType.DESCRIPTION, previousDescription, newDescription)));
    }

    public void createdComment(int cardId, CardData comment, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(
                new EventToRun(this, event, projectShortName, user, payloadFor(cardId, "comment", comment)));
    }

    public void updatedComment(int cardId, CardData previousComment, String newComment, User user,
            LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                updateFor(cardId, previousComment, newComment)));
    }

    public void deletedComment(int cardId, CardData deletedComment, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(
                new EventToRun(this, event, projectShortName, user, payloadFor(cardId, "comment", deletedComment)));
    }

    public void undeletedComment(int cardId, CardData undeletedComment, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(new EventToRun(this, event, projectShortName, user,
                payloadFor(cardId, "comment", undeletedComment)));
    }

    public void uploadedFile(int cardId, List<String> fileNames, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "files", fileNames);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void deletedFile(int cardId, String fileName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "file", fileName);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void undoDeletedFile(int cardId, String fileName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "file", fileName);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void removedLabelValueToCards(List<CardFull> affectedCards, int labelId, LabelValue labelValue,
            User user, LavagnaEvent event) {
        handleLabelValue(affectedCards, labelId, labelValue, user, event);
    }

    public void addLabelValueToCards(List<CardFull> affectedCards, int labelId, LabelValue labelValue, User user,
            LavagnaEvent event) {
        handleLabelValue(affectedCards, labelId, labelValue, user, event);
    }

    public void updateLabelValueToCards(List<CardFull> updated, int labelId, LabelValue labelValue, User user,
            LavagnaEvent event) {
        handleLabelValue(updated, labelId, labelValue, user, event);
    }

    private void handleLabelValue(List<CardFull> affectedCards, int labelId, LabelValue labelValue, User user,
            LavagnaEvent event) {
        if (affectedCards.isEmpty()) {
            return;
        }

        String projectShortName = projectService.findRelatedProjectShortNameByLabelId(labelId);
        Map<String, Object> payload = new HashMap<>();
        Label label = from(labelService.findLabelById(labelId), labelValue);
        payload.put("affectedCards", toList(affectedCards));
        payload.put("label", label);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    private Label from(CardLabel cardLabel, LabelValue labelValue) {
        Object value = null;
        switch (cardLabel.getType()) {
        case CARD:
            value = From.from(cardService.findFullBy(labelValue.getValueCard()), baseUrl());
            break;
        case INT:
            value = labelValue.getValueInt();
            break;
        case LIST:
            value = labelService.findLabelListValueById(labelValue.getValueList()).getValue();
            break;
        case STRING:
            value = labelValue.getValueString();
            break;
        case TIMESTAMP:
            value = Json.formatDate(labelValue.getValueTimestamp());
            break;
        case USER:
            value = From.from(userService.findUserWithPermission(labelValue.getValueUser()));
            break;
        }
        return new Label(cardLabel.getType().toString(), cardLabel.getDomain().toString(), cardLabel.getName(),
                value);
    }

    private String baseUrl() {
        return configurationRepository.getValue(Key.BASE_APPLICATION_URL);
    }

    private List<io.lavagna.model.apihook.Card> toList(List<CardFull> cards) {
        List<io.lavagna.model.apihook.Card> res = new ArrayList<>(cards.size());
        String baseUrl = baseUrl();
        for (CardFull cf : cards) {
            res.add(From.from(cf, baseUrl));
        }
        return res;
    }

    private void handleActionList(int cardId, String name, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "actionList", name);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void createActionList(int cardId, String name, User user, LavagnaEvent event) {
        handleActionList(cardId, name, user, event);
    }

    public void deleteActionList(int cardId, String name, User user, LavagnaEvent event) {
        handleActionList(cardId, name, user, event);
    }

    public void updatedNameActionList(int cardId, String oldName, String newName, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        executor.execute(new EventToRun(this, event, projectShortName, user, updateFor(cardId, oldName, newName)));
    }

    public void undeletedActionList(int cardId, String name, User user, LavagnaEvent event) {
        handleActionList(cardId, name, user, event);
    }

    public void createActionItem(int cardId, String actionItemListName, String actionItem, User user,
            LavagnaEvent event) {
        handleActionItem(cardId, actionItemListName, actionItem, user, event);
    }

    public void deletedActionItem(int cardId, String actionItemListName, String actionItem, User user,
            LavagnaEvent event) {
        handleActionItem(cardId, actionItemListName, actionItem, user, event);
    }

    public void toggledActionItem(int cardId, String actionItemListName, String actionItem, boolean toggle,
            User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "actionList", actionItemListName);
        payload.put("actionItem", actionItem);
        payload.put("toggled", toggle);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void updatedActionItem(int cardId, String actionItemListName, String oldActionItem, String newActionItem,
            User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = updateFor(cardId, oldActionItem, newActionItem);
        payload.put("actionList", actionItemListName);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void undoDeleteActionItem(int cardId, String actionItemListName, String actionItem, User user,
            LavagnaEvent event) {
        handleActionItem(cardId, actionItemListName, actionItem, user, event);
    }

    private void handleActionItem(int cardId, String actionItemListName, String actionItem, User user,
            LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "actionList", actionItemListName);
        payload.put("actionItem", actionItem);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void movedActionItem(int cardId, String fromActionItemListName, String toActionItemListName,
            String actionItem, User user, LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardId);
        Map<String, Object> payload = payloadFor(cardId, "actionItem", actionItem);
        payload.put("from", fromActionItemListName);
        payload.put("to", toActionItemListName);
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

    public void moveCards(BoardColumn from, BoardColumn to, Collection<Integer> cardIds, User user,
            LavagnaEvent event) {
        String projectShortName = projectService.findRelatedProjectShortNameByCardId(cardIds.iterator().next());
        Map<String, Object> payload = new HashMap<>();
        payload.put("affectedCards", toList(cardService.findFullBy(cardIds)));
        payload.put("from", From.from(from));
        payload.put("to", From.from(to));
        executor.execute(new EventToRun(this, event, projectShortName, user, payload));
    }

}