com.github.kaitoy.goslings.server.dao.jgit.ObjectDaoImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.github.kaitoy.goslings.server.dao.jgit.ObjectDaoImpl.java

Source

/*
 * Goslings - Git Repository Visualizer
 * https://github.com/kaitoy/goslings
 * MIT licensed
 *
 * Copyright (C) 2016 Kaito Yamada
 */

package com.github.kaitoy.goslings.server.dao.jgit;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.StreamSupport;

import javax.xml.bind.DatatypeConverter;

import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;

import com.github.kaitoy.goslings.server.BeanQualifiers;
import com.github.kaitoy.goslings.server.dao.DaoException;
import com.github.kaitoy.goslings.server.dao.ObjectDao;
import com.github.kaitoy.goslings.server.resource.Commit;
import com.github.kaitoy.goslings.server.resource.Tree;

/**
 * Implementation of {@link ObjectDao} by JGit.
 *
 * @author Kaito Yamada
 */
@Repository
@Qualifier(BeanQualifiers.DAO_JGIT)
public final class ObjectDaoImpl implements ObjectDao {

    private static final Logger LOG = LoggerFactory.getLogger(ObjectDaoImpl.class);
    private static final RepositoryResolver resolver = RepositoryResolver.getInstance();

    @Override
    public Commit[] getCommits(String token) {
        try {
            return StreamSupport.stream(resolver.getGit(token).log().all().call().spliterator(), false)
                    .map(this::convertToCommit).toArray(Commit[]::new);
        } catch (NoHeadException e) {
            String message = new StringBuilder().append("Failed to get commits in the repository ").append(token)
                    .append(" because it doesn't have HEAD.").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        } catch (GitAPIException e) {
            String message = new StringBuilder().append("Failed to get commits in the repository ").append(token)
                    .append(" due to an error of the Git command.").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        } catch (IOException e) {
            String message = new StringBuilder().append("Failed to get commits in the repository ").append(token)
                    .append(" due to an I/O error.").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        }
    }

    @Override
    @Cacheable
    public Tree[] getTrees(String token, String[] objectIds) throws DaoException {
        try (RevWalk walk = new RevWalk(resolver.getRepository(token))) {
            List<Tree> trees = new ArrayList<>(objectIds.length);
            for (String objectId : objectIds) {
                try {
                    RevObject obj = walk.parseAny(ObjectId.fromString(objectId));
                    if (obj.getType() != Constants.OBJ_TREE) {
                        String message = new StringBuilder().append("Failed to get a tree in the repository ")
                                .append(token).append(". ").append(objectId).append(" is not a tree.").toString();
                        LOG.error(message + "It's {}.", obj.getClass());
                        throw new DaoException(message);
                    }
                    trees.add(convertToTree(token, (RevTree) obj));
                } catch (MissingObjectException e) {
                    String message = new StringBuilder().append("Failed to get a tree in the repository ")
                            .append(token).append(". ").append(objectId).append(" doesn't exist.").toString();
                    LOG.error(message);
                    throw new DaoException(message, e);
                } catch (IOException e) {
                    String message = new StringBuilder().append("Failed to get a tree ").append(objectId)
                            .append(" in the repository ").append(token).append(" due to an I/O error.").toString();
                    LOG.error(message);
                    throw new DaoException(message, e);
                }
            }
            return trees.toArray(new Tree[objectIds.length]);
        }
    }

    @Override
    @Cacheable
    public String getContents(String token, String objectId) {
        RawContents rawContents = getRawContents(token, objectId);
        if (rawContents.type == Constants.OBJ_TREE) {
            try {
                List<TreeEntry> entries = parseTree(rawContents.contents);
                StringBuilder sb = new StringBuilder();
                for (TreeEntry entry : entries) {
                    sb.append(entry.mode).append(entry.isTree() ? " tree " : " blob ").append(entry.id).append(" ")
                            .append(entry.name).append("\n");
                }
                return sb.toString();
            } catch (IOException e) {
                String message = new StringBuilder().append("Filed to get contents of the tree ").append(objectId)
                        .append(" in the repository ").append(token).append(" due to an I/O error.").toString();
                LOG.error(message, e);
                throw new DaoException(message, e);
            }
        } else {
            return new String(rawContents.contents);
        }
    }

    @Cacheable
    private RawContents getRawContents(String token, String objectId) {
        try {
            ObjectLoader loader = resolver.getRepository(token).open(ObjectId.fromString(objectId));
            return new RawContents(loader.getType(), loader.getBytes());
        } catch (MissingObjectException e) {
            String message = new StringBuilder().append("The specified object ").append(objectId)
                    .append(" doesn't exist in the repository ").append(token).append(".").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        } catch (IOException e) {
            String message = new StringBuilder().append("Failed to get contents of the specified object ")
                    .append(objectId).append(" in the repository ").append(token).append(".").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        }
    }

    private Commit convertToCommit(RevCommit commit) {
        return new Commit(commit.getName(),
                Arrays.stream(commit.getParents()).map(parent -> parent.getName()).toArray(String[]::new),
                commit.getTree().getName());
    }

    private Tree convertToTree(String token, RevTree tree) {
        byte[] rawContents = getRawContents(token, tree.getName()).contents;
        try {
            List<TreeEntry> entries = parseTree(rawContents);
            Map<String, String> trees = new HashMap<>();
            Map<String, String> blobs = new HashMap<>();
            for (TreeEntry entry : entries) {
                if (entry.isTree()) {
                    trees.put(entry.id, entry.name);
                } else {
                    blobs.put(entry.id, entry.name);
                }
            }
            return new Tree(tree.getName(), trees, blobs);
        } catch (IOException e) {
            String message = new StringBuilder().append("Filed to get the tree ").append(tree.getName())
                    .append(" in the repository ").append(token).append(" due to an I/O error.").toString();
            LOG.error(message, e);
            throw new DaoException(message, e);
        }
    }

    @Cacheable
    private List<TreeEntry> parseTree(byte[] tree) throws IOException {
        List<TreeEntry> entries = new ArrayList<>();
        ByteArrayInputStream in = new ByteArrayInputStream(tree);
        byte[] mode = new byte[6];
        ByteArrayOutputStream nameStream = new ByteArrayOutputStream();
        byte[] rawId = new byte[20];
        while (in.available() > 0) {
            in.read(mode);
            String modeStr = new String(mode);
            if (modeStr.equals("40000 ")) {
                modeStr = "040000";
            } else {
                in.skip(1);
            }

            int nameByte;
            while ((nameByte = in.read()) != 0x00) {
                nameStream.write(nameByte);
            }
            String name = new String(nameStream.toByteArray());

            in.read(rawId);
            String id = DatatypeConverter.printHexBinary(rawId).toLowerCase();

            entries.add(new TreeEntry(modeStr, id, name));
            nameStream.reset();
        }

        return entries;
    }

    private static final class RawContents {

        private final int type;
        private final byte[] contents;

        private RawContents(int type, byte[] contents) {
            this.type = type;
            this.contents = contents;
        }

    }

    private static final class TreeEntry {

        private final String mode;
        private final String id;
        private final String name;

        TreeEntry(String mode, String id, String name) {
            this.mode = mode;
            this.id = id;
            this.name = name;
        }

        private boolean isTree() {
            return mode.equals("040000");
        }

    }

}