org.cfg4j.source.git.GitConfigurationSource.java Source code

Java tutorial

Introduction

Here is the source code for org.cfg4j.source.git.GitConfigurationSource.java

Source

/*
 * Copyright 2015-2016 Norbert Potocki (norbert.potocki@nort.pl)
 *
 * 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 org.cfg4j.source.git;

import static java.util.Objects.requireNonNull;

import org.cfg4j.source.ConfigurationSource;
import org.cfg4j.source.SourceCommunicationException;
import org.cfg4j.source.context.environment.Environment;
import org.cfg4j.source.context.environment.MissingEnvironmentException;
import org.cfg4j.source.context.filesprovider.ConfigFilesProvider;
import org.cfg4j.source.context.propertiesprovider.PropertiesProvider;
import org.cfg4j.source.context.propertiesprovider.PropertiesProviderSelector;
import org.cfg4j.utils.FileUtils;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Note: use {@link GitConfigurationSourceBuilder} for building instances of this class.
 * <p>
 * Read configuration from the remote GIT repository. Keeps a local clone of the repository.
 */
class GitConfigurationSource implements ConfigurationSource, Closeable {

    private static final Logger LOG = LoggerFactory.getLogger(GitConfigurationSource.class);

    private final BranchResolver branchResolver;
    private final PathResolver pathResolver;
    private final ConfigFilesProvider configFilesProvider;
    private final PropertiesProviderSelector propertiesProviderSelector;
    private final String repositoryURI;
    private final Path tmpPath;
    private final String tmpRepoPrefix;
    private Git clonedRepo;
    private Path clonedRepoPath;
    private boolean initialized;

    /**
     * Note: use {@link GitConfigurationSourceBuilder} for building instances of this class.
     * <p>
     * Read configuration from the remote GIT repository residing at {@code repositoryURI}. Keeps a local
     * clone of the repository in the {@code tmpRepoPrefix} directory under {@code tmpPath} path.
     * Uses provided {@code branchResolver} and {@code pathResolver} for branch and path resolution.
     *
     * @param repositoryURI              URI to the remote git repository
     * @param tmpPath                    path to the tmp directory
     * @param tmpRepoPrefix              prefix for the name of the local directory keeping the repository clone
     * @param branchResolver             {@link BranchResolver} used for extracting git branch from an {@link Environment}
     * @param pathResolver               {@link PathResolver} used for extracting git path from an {@link Environment}
     * @param configFilesProvider        {@link ConfigFilesProvider} used for determining which files in repository should be read
     * @param propertiesProviderSelector selector used for choosing {@link PropertiesProvider} based on a configuration file extension
     *                                   as config files
     */
    GitConfigurationSource(String repositoryURI, Path tmpPath, String tmpRepoPrefix, BranchResolver branchResolver,
            PathResolver pathResolver, ConfigFilesProvider configFilesProvider,
            PropertiesProviderSelector propertiesProviderSelector) {
        this.branchResolver = requireNonNull(branchResolver);
        this.pathResolver = requireNonNull(pathResolver);
        this.configFilesProvider = requireNonNull(configFilesProvider);
        this.propertiesProviderSelector = requireNonNull(propertiesProviderSelector);
        this.repositoryURI = requireNonNull(repositoryURI);
        this.tmpPath = requireNonNull(tmpPath);
        this.tmpRepoPrefix = requireNonNull(tmpRepoPrefix);

        initialized = false;
    }

    @Override
    public Properties getConfiguration(Environment environment) {
        if (!initialized) {
            throw new IllegalStateException(
                    "Configuration source has to be successfully initialized before you request configuration.");
        }

        reload();

        try {
            checkoutToBranch(branchResolver.getBranchNameFor(environment));
        } catch (GitAPIException e) {
            throw new MissingEnvironmentException(environment.getName(), e);
        }

        Properties properties = new Properties();

        List<Path> paths = new ArrayList<>();
        for (Path path : configFilesProvider.getConfigFiles()) {
            paths.add(clonedRepoPath.resolve(pathResolver.getPathFor(environment)).resolve(path));
        }

        for (Path path : paths) {
            try (InputStream input = new FileInputStream(path.toFile())) {

                PropertiesProvider provider = propertiesProviderSelector.getProvider(path.getFileName().toString());
                properties.putAll(provider.getProperties(input));

            } catch (IOException e) {
                throw new IllegalStateException("Unable to load configuration from " + path.toString() + " file",
                        e);
            }
        }

        return properties;
    }

    /**
     * @throws IllegalStateException        when unable to create directories for local repo clone
     * @throws SourceCommunicationException when unable to clone repository
     */
    @Override
    public void init() {
        LOG.info("Initializing " + GitConfigurationSource.class + " pointing to " + repositoryURI);

        try {
            clonedRepoPath = Files.createTempDirectory(tmpPath, tmpRepoPrefix);
            // This folder can't exist or JGit will throw NPE on clone
            Files.delete(clonedRepoPath);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to create local clone directory: " + tmpRepoPrefix, e);
        }

        try {
            clonedRepo = Git.cloneRepository().setURI(repositoryURI).setDirectory(clonedRepoPath.toFile()).call();
        } catch (GitAPIException e) {
            throw new SourceCommunicationException("Unable to clone repository: " + repositoryURI, e);
        }

        initialized = true;
    }

    private void reload() {
        try {
            LOG.debug("Reloading configuration by pulling changes");
            clonedRepo.pull().call();
        } catch (GitAPIException e) {
            initialized = false;
            throw new IllegalStateException("Unable to pull from remote repository", e);
        }
    }

    @Override
    public void close() throws IOException {
        LOG.debug("Closing local repository: " + clonedRepoPath);
        clonedRepo.close();
        new FileUtils().deleteDir(clonedRepoPath);
    }

    private void checkoutToBranch(String branch) throws GitAPIException {
        CheckoutCommand checkoutCommand = clonedRepo.checkout().setCreateBranch(false).setName(branch);

        List<Ref> refList = clonedRepo.branchList().call();
        if (!anyRefMatches(refList, branch)) {
            checkoutCommand = checkoutCommand.setCreateBranch(true)
                    .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK).setStartPoint("origin/" + branch);
        }

        checkoutCommand.call();
    }

    private boolean anyRefMatches(List<Ref> refList, String branch) {
        for (Ref ref : refList) {
            if (ref.getName().replace("refs/heads/", "").equals(branch)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public String toString() {
        return "GitConfigurationSource{" + "clonedRepo=" + clonedRepo + ", clonedRepoPath=" + clonedRepoPath
                + ", branchResolver=" + branchResolver + ", pathResolver=" + pathResolver + ", configFilesProvider="
                + configFilesProvider + '}';
    }
}