alfio.extension.ExtensionService.java Source code

Java tutorial

Introduction

Here is the source code for alfio.extension.ExtensionService.java

Source

/**
 * This file is part of alf.io.
 *
 * alf.io 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.
 *
 * alf.io 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 alf.io.  If not, see <http://www.gnu.org/licenses/>.
 */

package alfio.extension;

import alfio.model.Event;
import alfio.model.ExtensionLog;
import alfio.model.ExtensionSupport;
import alfio.model.ExtensionSupport.*;
import alfio.model.user.Organization;
import alfio.repository.ExtensionLogRepository;
import alfio.repository.ExtensionRepository;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

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

@Service
@Log4j2
@AllArgsConstructor
public class ExtensionService {

    private final ScriptingExecutionService scriptingExecutionService;

    private final ExtensionRepository extensionRepository;

    private final ExtensionLogRepository extensionLogRepository;

    private final PlatformTransactionManager platformTransactionManager;

    @AllArgsConstructor
    private static final class ExtensionLoggerImpl implements ExtensionLogger {

        private final ExtensionLogRepository extensionLogRepository;
        private final PlatformTransactionManager platformTransactionManager;
        private final String effectivePath;
        private final String path;
        private final String name;

        @Override
        public void logWarning(String msg) {
            executeInNewTransaction((s) -> extensionLogRepository.insert(effectivePath, path, name, msg,
                    ExtensionLog.Type.WARNING));
        }

        @Override
        public void logSuccess(String msg) {
            executeInNewTransaction((s) -> extensionLogRepository.insert(effectivePath, path, name, msg,
                    ExtensionLog.Type.SUCCESS));
        }

        @Override
        public void logError(String msg) {
            executeInNewTransaction(
                    (s) -> extensionLogRepository.insert(effectivePath, path, name, msg, ExtensionLog.Type.ERROR));
        }

        @Override
        public void logInfo(String msg) {
            executeInNewTransaction(
                    (s) -> extensionLogRepository.insert(effectivePath, path, name, msg, ExtensionLog.Type.INFO));
        }

        private void executeInNewTransaction(TransactionCallback<Integer> t) {
            DefaultTransactionDefinition definition = new DefaultTransactionDefinition(
                    TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            TransactionTemplate template = new TransactionTemplate(platformTransactionManager, definition);
            template.execute(t);
        }
    }

    private static final class NoopExtensionLogger implements ExtensionLogger {
    }

    private static ExtensionMetadata getMetadata(String name, String script) {
        return ScriptingExecutionService.executeScript(name,
                script + "\n;GSON.fromJson(JSON.stringify(getScriptMetadata()), returnClass);", //<- ugly hack, but the interop java<->js is simpler that way...
                Collections.emptyMap(), ExtensionMetadata.class, new NoopExtensionLogger());
    }

    @Transactional
    public void createOrUpdate(String previousPath, String previousName, Extension script) {
        Validate.notBlank(script.getName(), "Name is mandatory");
        Validate.notBlank(script.getPath(), "Path must be defined");
        String hash = DigestUtils.sha256Hex(script.getScript());
        ExtensionMetadata extensionMetadata = getMetadata(script.getName(), script.getScript());

        extensionRepository.deleteEventsForPath(previousPath, previousName);

        if (!Objects.equals(previousPath, script.getPath()) || !Objects.equals(previousName, script.getName())) {
            extensionRepository.deleteScriptForPath(previousPath, previousName);
            extensionRepository.insert(script.getPath(), script.getName(), hash, script.isEnabled(),
                    extensionMetadata.async, script.getScript());
        } else {
            extensionRepository.update(script.getPath(), script.getName(), hash, script.isEnabled(),
                    extensionMetadata.async, script.getScript());
            //TODO: load all saved parameters value, then delete the register extension parameter
        }

        int extensionId = extensionRepository.getExtensionIdFor(script.getPath(), script.getName());

        for (String event : extensionMetadata.events) {
            extensionRepository.insertEvent(extensionId, event);
        }

        //
        ExtensionMetadata.Parameters parameters = extensionMetadata.getParameters();
        if (parameters != null) {
            extensionRepository.deleteExtensionParameter(extensionId);
            //TODO: handle if already present, cleanup key that are no more present
            for (ExtensionMetadata.Field field : parameters.getFields()) {
                for (String level : parameters.getConfigurationLevels()) {
                    extensionRepository.registerExtensionConfigurationMetadata(extensionId, field.getName(),
                            field.getDescription(), field.getType(), level, field.isRequired());
                    //if for this key,level is present a value -> save
                }
            }
        }
    }

    public List<ExtensionParameterMetadataAndValue> getConfigurationParametersFor(String basePath,
            String pathPattern, String configurationLevel) {
        return extensionRepository.getParametersForLevelAndPath(configurationLevel, generatePossiblePath(basePath),
                pathPattern);
    }

    @Transactional
    public void bulkUpdateSystemSettings(List<ExtensionMetadataValue> toUpdate) {
        deleteAndInsertSetting("SYSTEM", "-", toUpdate);
    }

    @Transactional
    public void bulkUpdateOrganizationSettings(Organization org, List<ExtensionMetadataValue> toUpdate) {
        String path = "-" + org.getId();
        deleteAndInsertSetting("ORGANIZATION", path, toUpdate);
    }

    @Transactional
    public void bulkUpdateEventSettings(Organization org, Event event, List<ExtensionMetadataValue> toUpdate) {
        String path = "-" + org.getId() + "-" + event.getId();
        deleteAndInsertSetting("EVENT", path, toUpdate);
    }

    @Transactional
    public void deleteSettingValue(int id, String path) {
        extensionRepository.deleteSettingValue(id, path);
    }

    private void deleteAndInsertSetting(String level, String path, List<ExtensionMetadataValue> toUpdate) {
        extensionRepository.deleteSettingValue(level, path);
        List<ExtensionMetadataValue> toUpdate2 = (toUpdate == null ? Collections.emptyList() : toUpdate);
        List<ExtensionMetadataValue> filtered = toUpdate2.stream()
                .filter(f -> StringUtils.trimToNull(f.getValue()) != null).collect(Collectors.toList());
        for (ExtensionMetadataValue v : filtered) {
            extensionRepository.insertSettingValue(v.getId(), path, v.getValue());
        }
    }

    @Transactional
    public void toggle(String path, String name, boolean status) {
        extensionRepository.toggle(path, name, status);
    }

    @Transactional
    public void delete(String path, String name) {
        extensionRepository.deleteEventsForPath(path, name);
        extensionRepository.deleteScriptForPath(path, name);
    }

    @Transactional(readOnly = true)
    public String getScript(String path, String name) {
        return extensionRepository.getScript(path, name);
    }

    @Transactional(readOnly = true)
    public Optional<ExtensionSupport> getSingle(String path, String name) {
        return extensionRepository.getSingle(path, name);
    }

    public <T> T executeScriptsForEvent(String event, String basePath, Map<String, Object> payload,
            Class<T> clazz) {
        List<ScriptPathNameHash> activePaths = getActiveScriptsForEvent(event, basePath, false);
        T res = null;
        Map<String, Object> input = new HashMap<>(payload);
        input.put("extensionEvent", event);
        for (ScriptPathNameHash activePath : activePaths) {
            String path = activePath.getPath();
            String name = activePath.getName();
            res = scriptingExecutionService.executeScript(name, activePath.getHash(),
                    () -> getScript(path, name)
                            + "\n;GSON.fromJson(JSON.stringify(executeScript(extensionEvent)), returnClass);",
                    input, clazz, new ExtensionLoggerImpl(extensionLogRepository, platformTransactionManager,
                            basePath, path, name));
            input.put("output", res);
        }
        return res;
    }

    public void executeScriptAsync(String event, String basePath, Map<String, Object> payload) {
        List<ScriptPathNameHash> activePaths = getActiveScriptsForEvent(event, basePath, true);
        Map<String, Object> input = new HashMap<>(payload);
        input.put("extensionEvent", event);
        for (ScriptPathNameHash activePath : activePaths) {
            String path = activePath.getPath();
            String name = activePath.getName();
            scriptingExecutionService.executeScriptAsync(path, name, activePath.getHash(),
                    () -> getScript(path, name) + "\n;executeScript(extensionEvent);", input,
                    new ExtensionLoggerImpl(extensionLogRepository, platformTransactionManager, basePath, path,
                            name));
        }
    }

    private List<ScriptPathNameHash> getActiveScriptsForEvent(String event, String basePath, boolean async) {
        // fetch all active scripts
        // to handle override:
        // if there are active tree scripts with the same name
        // with path:
        //  - -org-event
        //  - -org
        //  - -
        // the one with the longest path win
        Set<String> paths = generatePossiblePath(basePath);
        return extensionRepository.findActive(paths, async, event);
    }

    private static Set<String> generatePossiblePath(String basePath) {
        //generate all the paths
        // given "-0-0" it will generate
        // "-", "-0", "-0-0"
        Set<String> paths = new TreeSet<>();
        int basePathLength = basePath.length();
        for (int i = 1; i < basePathLength; i++) {
            if (basePath.charAt(i) == '-') {
                paths.add(basePath.substring(0, i));
            }
        }
        paths.add("-"); //handle first and last case
        paths.add(basePath);
        return paths;
    }

    @Transactional(readOnly = true)
    public List<ExtensionSupport> listAll() {
        return extensionRepository.listAll();
    }

    @Transactional(readOnly = true)
    public Pair<List<ExtensionLog>, Integer> getLog(String path, String name, ExtensionLog.Type type, int pageSize,
            int offset) {
        int count = extensionLogRepository.countPages(path, name, type);
        List<ExtensionLog> logs = extensionLogRepository.getPage(path, name, type, pageSize, offset);
        return Pair.of(logs, count);
    }
}