org.springframework.cloud.config.server.JGitEnvironmentRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.config.server.JGitEnvironmentRepository.java

Source

/*
 * Copyright 2013-2015 the original author or authors.
 *
 * 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.springframework.cloud.config.server;

import static org.springframework.util.StringUtils.hasText;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.ListBranchCommand.ListMode;
import org.eclipse.jgit.api.PullCommand;
import org.eclipse.jgit.api.TransportCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.FileUtils;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.jcraft.jsch.Session;

/**
 * An {@link EnvironmentRepository} backed by a single git repository.
 *
 * @author Dave Syer
 * @author Roy Clarkson
 */
public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository {

    private static Log logger = LogFactory.getLog(JGitEnvironmentRepository.class);

    private static final String DEFAULT_LABEL = "master";
    private static final String FILE_URI_PREFIX = "file:";

    private boolean initialized;

    private boolean cloneOnStart = false;

    private JGitEnvironmentRepository.JGitFactory gitFactory = new JGitEnvironmentRepository.JGitFactory();

    public JGitEnvironmentRepository(ConfigurableEnvironment environment) {
        super(environment);
    }

    public boolean isCloneOnStart() {
        return this.cloneOnStart;
    }

    public void setCloneOnStart(boolean cloneOnStart) {
        this.cloneOnStart = cloneOnStart;
    }

    public JGitFactory getGitFactory() {
        return this.gitFactory;
    }

    public void setGitFactory(JGitFactory gitFactory) {
        this.gitFactory = gitFactory;
    }

    @Override
    public String getDefaultLabel() {
        return DEFAULT_LABEL;
    }

    @Override
    public Environment findOne(String application, String profile, String label) {
        initialize();
        Git git = null;
        try {
            git = createGitClient();
            return loadEnvironment(git, application, profile, label);
        } catch (RefNotFoundException e) {
            throw new NoSuchLabelException("No such label: " + label);
        } catch (GitAPIException e) {
            throw new IllegalStateException("Cannot clone or checkout repository", e);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load environment", e);
        } finally {
            try {
                if (git != null) {
                    git.getRepository().close();
                }
            } catch (Exception e) {
                logger.warn("Could not close git repository", e);
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.state(getUri() != null, "You need to configure a uri for the git repository");
        if (this.cloneOnStart) {
            initClonedRepository();
        }
    }

    /**
     * Clones the remote repository and then opens a connection to it.
     * @throws GitAPIException
     * @throws IOException
     */
    private void initClonedRepository() throws GitAPIException, IOException {
        if (!getUri().startsWith(FILE_URI_PREFIX)) {
            deleteBaseDirIfExists();
            cloneToBasedir();
            openGitRepository();
        }

    }

    private synchronized Environment loadEnvironment(Git git, String application, String profile, String label)
            throws GitAPIException {
        NativeEnvironmentRepository environment = new NativeEnvironmentRepository(getEnvironment());
        git.getRepository().getConfig().setString("branch", label, "merge", label);
        Ref ref = checkout(git, label);
        if (shouldPull(git, ref)) {
            pull(git, label, ref);
        }
        environment.setSearchLocations(getSearchLocations(getWorkingDirectory()));
        Environment result = environment.findOne(application, profile, "");
        result.setLabel(label);
        return clean(result);
    }

    private Ref checkout(Git git, String label) throws GitAPIException {
        CheckoutCommand checkout = git.checkout();
        if (shouldTrack(git, label)) {
            trackBranch(git, checkout, label);
        } else {
            // works for tags and local branches
            checkout.setName(label);
        }
        return checkout.call();
    }

    private boolean shouldPull(Git git, Ref ref) throws GitAPIException {
        return git.status().call().isClean() && ref != null
                && git.getRepository().getConfig().getString("remote", "origin", "url") != null;
    }

    private boolean shouldTrack(Git git, String label) throws GitAPIException {
        return isBranch(git, label) && !isLocalBranch(git, label);
    }

    /**
     * Assumes we are on a tracking branch (should be safe)
     */
    private void pull(Git git, String label, Ref ref) {
        PullCommand pull = git.pull();
        try {
            if (hasText(getUsername())) {
                setCredentialsProvider(pull);
            }
            pull.call();
        } catch (Exception e) {
            logger.warn("Could not pull remote for " + label + " (current ref=" + ref + "), remote: "
                    + git.getRepository().getConfig().getString("remote", "origin", "url"));
        }
    }

    private Git createGitClient() throws IOException, GitAPIException {
        if (new File(getBasedir(), ".git").exists()) {
            return openGitRepository();
        } else {
            return copyRepository();
        }
    }

    private Git copyRepository() throws IOException, GitAPIException {
        deleteBaseDirIfExists();
        getBasedir().mkdirs();
        Assert.state(getBasedir().exists(), "Could not create basedir: " + getBasedir());
        if (getUri().startsWith(FILE_URI_PREFIX)) {
            return copyFromLocalRepository();
        } else {
            return cloneToBasedir();
        }
    }

    private Git openGitRepository() throws IOException {
        Git git = this.gitFactory.getGitByOpen(getWorkingDirectory());
        tryFetch(git);
        return git;
    }

    private Git copyFromLocalRepository() throws IOException {
        Git git;
        File remote = new UrlResource(StringUtils.cleanPath(getUri())).getFile();
        Assert.state(remote.isDirectory(), "No directory at " + getUri());
        File gitDir = new File(remote, ".git");
        Assert.state(gitDir.exists(), "No .git at " + getUri());
        Assert.state(gitDir.isDirectory(), "No .git directory at " + getUri());
        git = this.gitFactory.getGitByOpen(remote);
        return git;
    }

    private Git cloneToBasedir() throws GitAPIException {
        CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository().setURI(getUri())
                .setDirectory(getBasedir());
        if (hasText(getUsername())) {
            setCredentialsProvider(clone);
        }
        return clone.call();
    }

    private void tryFetch(Git git) {
        try {
            FetchCommand fetch = git.fetch();
            if (hasText(getUsername())) {
                setCredentialsProvider(fetch);
            }
            fetch.call();
        } catch (Exception e) {
            logger.warn("Remote repository not available");
        }
    }

    private void deleteBaseDirIfExists() {
        if (getBasedir().exists()) {
            try {
                FileUtils.delete(getBasedir(), FileUtils.RECURSIVE);
            } catch (IOException e) {
                throw new IllegalStateException("Failed to initialize base directory", e);
            }
        }
    }

    private void initialize() {
        if (getUri().startsWith("file:") && !this.initialized) {
            SshSessionFactory.setInstance(new JschConfigSessionFactory() {
                @Override
                protected void configure(Host hc, Session session) {
                    session.setConfig("StrictHostKeyChecking", "no");
                }
            });
            this.initialized = true;
        }
    }

    private void setCredentialsProvider(TransportCommand<?, ?> cmd) {
        cmd.setCredentialsProvider(new UsernamePasswordCredentialsProvider(getUsername(), getPassword()));
    }

    private void trackBranch(Git git, CheckoutCommand checkout, String label) {
        checkout.setCreateBranch(true).setName(label).setUpstreamMode(SetupUpstreamMode.TRACK)
                .setStartPoint("origin/" + label);
    }

    private boolean isBranch(Git git, String label) throws GitAPIException {
        return containsBranch(git, label, ListMode.ALL);
    }

    private boolean isLocalBranch(Git git, String label) throws GitAPIException {
        return containsBranch(git, label, null);
    }

    private boolean containsBranch(Git git, String label, ListMode listMode) throws GitAPIException {
        ListBranchCommand command = git.branchList();
        if (listMode != null) {
            command.setListMode(listMode);
        }
        List<Ref> branches = command.call();
        for (Ref ref : branches) {
            if (ref.getName().endsWith("/" + label)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Wraps the static method calls to {@link org.eclipse.jgit.api.Git} and
     * {@link org.eclipse.jgit.api.CloneCommand} allowing for easier unit
     * testing.
     */
    static class JGitFactory {

        public Git getGitByOpen(File file) throws IOException {
            return Git.open(file);
        }

        public CloneCommand getCloneCommandByCloneRepository() {
            return Git.cloneRepository();
        }
    }
}