dollar.plugins.pipe.GithubModuleResolver.java Source code

Java tutorial

Introduction

Here is the source code for dollar.plugins.pipe.GithubModuleResolver.java

Source

/*
 *    Copyright (c) 2014-2017 Neil Ellis
 *
 *    Licensed 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 dollar.plugins.pipe;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import dollar.api.DollarException;
import dollar.api.DollarStatic;
import dollar.api.Pipeable;
import dollar.api.Scope;
import dollar.api.Value;
import dollar.api.VarFlags;
import dollar.api.VarKey;
import dollar.api.script.DollarParser;
import dollar.api.script.ModuleResolver;
import dollar.deps.DependencyRetriever;
import dollar.internal.runtime.script.parser.DollarParserImpl;
import dollar.internal.runtime.script.parser.scope.FileScope;
import dollar.internal.runtime.script.util.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static dollar.internal.runtime.script.DollarUtilFactory.util;
import static dollar.internal.runtime.script.util.FileUtil.delete;

public class GithubModuleResolver implements ModuleResolver {
    public static final int GRACEPERIOD = 10 * 1000;
    @NotNull
    private static final ExecutorService executor;
    @NotNull
    private static final Logger log = LoggerFactory.getLogger(GithubModuleResolver.class);
    @NotNull
    private static final LoadingCache<String, File> repos;

    static {
        executor = Executors.newSingleThreadExecutor();
        repos = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES)
                //                .removalListener((RemovalListener<String, File>) notification -> delete(notification.getValue()))
                .build(new CacheLoader<String, File>() {
                    @Override
                    @NotNull
                    public File load(@NotNull String key) throws ExecutionException, InterruptedException {

                        return executor.submit(() -> getFile(key)).get();

                    }

                });

    }

    @NotNull
    private static File getFile(@NotNull String uriWithoutScheme) throws IOException, InterruptedException {
        log.debug("GithubModuleResolver.getFile({})", uriWithoutScheme);

        String[] githubRepo = uriWithoutScheme.split(":");
        final String githubUser = githubRepo[0];
        final String branch = !githubRepo[2].isEmpty() ? githubRepo[2] : "master";

        final File dir = new File((FileUtil.SHARED_RUNTIME_PATH + "/modules/github") + "/" + githubUser + "/"
                + githubRepo[1] + "/" + branch);
        final String url = "https://github.com/" + githubRepo[0] + "/" + githubRepo[1] + ".git";
        final File lockFile = new File((FileUtil.SHARED_RUNTIME_PATH + "/modules/github") + "/." + githubUser + "."
                + githubRepo[1] + "." + branch + ".clone.lock");
        dir.mkdirs();

        if (lockFile.exists()) {
            log.debug("Lock file exists or branch ready.");
            //Git is annoyingly asynchronous so we wait to make sure the initial clone operation has completely finished
            if (lockFile.exists()) {
                log.debug("Lock file still exists so starting grace period before any operation");
                Thread.sleep(GRACEPERIOD);
            } else {
                Files.createFile(lockFile.toPath());
            }
            try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) {

                log.debug("Attempting to get lock file {}", lockFile);
                try (FileLock lock = channel.lock()) {
                    GitUtil.pull(dir);
                    lock.release();
                    log.debug("Lock file {} released", lockFile);
                } finally {
                    delete(lockFile);
                }

            } catch (OverlappingFileLockException e) {
                log.error(e.getMessage(), e);
                throw new DollarException("Attempted to update a module that is currently locked");
            }
        } else {
            log.debug("Lock file does not exist for module {} and it is not cloned, so we can assume initial state",
                    uriWithoutScheme);
            Files.createFile(lockFile.toPath());
            delete(dir);
            log.debug("Recreating dir");
            dir.mkdirs();

            try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) {
                try (FileLock lock = channel.lock()) {
                    GitUtil.clone(dir, url);
                    GitUtil.checkout(dir, branch);
                    lock.release();
                }
            }

        }

        return dir;
    }

    @NotNull
    @Override
    public ModuleResolver copy() {
        return this;
    }

    @NotNull
    @Override
    public String getScheme() {
        return "github";
    }

    @NotNull
    @Override
    public <T, P> Pipeable retrieveModule(@NotNull String uriWithoutScheme, @NotNull T scope, @NotNull P parser)
            throws Exception {
        log.debug(uriWithoutScheme);
        File dir = repos.get(uriWithoutScheme);

        String[] githubRepo = uriWithoutScheme.split(":");

        final ClassLoader classLoader;
        final String content;
        final File mainFile;

        if (githubRepo.length == 4) {

            classLoader = getClass().getClassLoader();
            mainFile = new File(dir, githubRepo[3]);
            content = new String(Files.readAllBytes(mainFile.toPath()));

        } else {

            final File moduleFile = new File(dir, "module.json");
            final Value module = DollarStatic.$(new String(Files.readAllBytes(moduleFile.toPath())));
            mainFile = new File(dir, module.$get(DollarStatic.$("main")).$S());
            content = new String(Files.readAllBytes(mainFile.toPath()));
            classLoader = DependencyRetriever.retrieve(module.$get(DollarStatic.$("dependencies")).$list()
                    .stream(false).map(t -> module.toString()).collect(Collectors.toList()));

        }
        return (params) -> util().inSubScope(false, false, "github-module", newScope -> {

            final ImmutableMap<Value, Value> paramMap = params[0].$map().toVarMap();
            for (Map.Entry<Value, Value> entry : paramMap.entrySet()) {
                newScope.set(VarKey.of(entry.getKey()), entry.getValue(), null, null,
                        new VarFlags(true, false, false, false, false, false));
            }
            return new DollarParserImpl(((DollarParser) parser).options(), classLoader)
                    .parse(new FileScope((Scope) scope, mainFile.getAbsolutePath(), content, "github-module-scope",
                            false, false), content);
        }).orElseThrow(() -> new AssertionError("Optional should not be null here"));
    }
}