com.smartitengineering.version.impl.jgit.JGitImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.smartitengineering.version.impl.jgit.JGitImpl.java

Source

/*
 * This is a common dao with basic CRUD operations and is not limited to any 
 * persistent layer implementation
 * 
 * Copyright (C) 2008  Imran M Yousuf (imyousuf@smartitengineering.com)
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This library 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
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.smartitengineering.version.impl.jgit;

import com.smartitengineering.dao.common.queryparam.QueryParameter;
import com.smartitengineering.version.api.Commit;
import com.smartitengineering.version.api.Resource;
import com.smartitengineering.version.api.Revision;
import com.smartitengineering.version.api.VersionedResource;
import com.smartitengineering.version.api.dao.VersionControlReadDao;
import com.smartitengineering.version.api.dao.VersionControlWriteDao;
import com.smartitengineering.version.api.dao.WriteStatus;
import com.smartitengineering.version.api.dao.WriterCallback;
import com.smartitengineering.version.api.factory.VersionAPI;
import com.smartitengineering.version.api.spi.MutableCommit;
import com.smartitengineering.version.api.spi.MutableRevision;
import com.smartitengineering.version.impl.jgit.service.MetaFactory;
import com.smartitengineering.version.impl.jgit.service.RCSConfig;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileTreeEntry;
import org.eclipse.jgit.lib.GitIndex;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectWriter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.Tree;
import org.eclipse.jgit.lib.TreeEntry;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;

/**
 *
 * @author imyousuf
 */
public class JGitImpl implements VersionControlWriteDao, VersionControlReadDao, JGitDaoExtension {

    private String readRepositoryLocation;
    private String writeRepositoryLocation;
    private Repository writeRepository;
    private ObjectWriter objectWriter;
    private Repository readRepository;
    private ExecutorService executorService;
    private RCSConfig config;
    private boolean initialized;

    public JGitImpl() {
    }

    public void init() throws IOException {
        if (initialized) {
            throw new IllegalStateException("Impl already initialized");
        }
        if (StringUtils.isBlank(getReadRepositoryLocation()) || StringUtils.isBlank(getWriteRepositoryLocation())) {
            throw new IllegalStateException("Repository location not set!");
        }
        File writeRepoDir = new File(getWriteRepositoryLocation());
        writeRepository = new Repository(writeRepoDir);
        if (!writeRepoDir.exists()) {
            writeRepository.create();
        }
        writeRepository.close();
        File readRepoDir = new File(getReadRepositoryLocation());
        readRepository = new Repository(readRepoDir);
        if (!readRepoDir.exists()) {
            readRepository.create();
        }
        readRepository.close();
        executorService = Executors.newFixedThreadPool(getConfig().getConcurrentWriteOperations());
        initialized = true;
    }

    public void finish() {
        if (writeRepository != null) {
            writeRepository.close();
        }
        if (readRepository != null) {
            readRepository.close();
        }
    }

    protected void checkInitialized() {
        if (!initialized) {
            throw new IllegalArgumentException(
                    "After constructing please " + "set repository location and then invoke init() before "
                            + "attempting to use any other operations");
        }
    }

    public RCSConfig getConfig() {
        if (config == null) {
            return MetaFactory.getInstance().getConfig();
        }
        return config;
    }

    public void setConfig(RCSConfig config) {
        this.config = config;
    }

    public void setRepositoryLocation(final String repositoryLocation) {
        if (StringUtils.isBlank(repositoryLocation)) {
            throw new IllegalArgumentException("Repo location can't be blank!");
        }
        this.readRepositoryLocation = repositoryLocation;
        this.writeRepositoryLocation = repositoryLocation;
    }

    public String getReadRepositoryLocation() {
        if (StringUtils.isBlank(readRepositoryLocation)) {
            return getConfig().getRepositoryReadPath();
        }
        return readRepositoryLocation;
    }

    public void setReadRepositoryLocation(final String readRepositoryLocation) {
        if (StringUtils.isBlank(readRepositoryLocation)) {
            throw new IllegalArgumentException("Repo location can't be blank!");
        }
        this.readRepositoryLocation = readRepositoryLocation;
    }

    public String getWriteRepositoryLocation() {
        if (StringUtils.isBlank(writeRepositoryLocation)) {
            return getConfig().getRepositoryWritePath();
        }
        return writeRepositoryLocation;
    }

    public void setWriteRepositoryLocation(final String writeRepositoryLocation) {
        if (StringUtils.isBlank(writeRepositoryLocation)) {
            throw new IllegalArgumentException("Repo location can't be blank!");
        }
        this.writeRepositoryLocation = writeRepositoryLocation;
    }

    public synchronized Repository getReadRepository() {
        return readRepository;
    }

    protected synchronized void reInitReadRepository() throws IOException {
        readRepository.close();
        readRepository = new Repository(new File(getReadRepositoryLocation()));
    }

    public Repository getWriteRepository() {
        return writeRepository;
    }

    public void store(final Commit commit, final WriterCallback callback) {
        executorService.submit(new Runnable() {

            public void run() {
                WriteStatus status = null;
                String comment = null;
                Throwable error = null;
                try {
                    Tree head = getHeadTree(writeRepository);
                    addOrUpdateToHead(commit, head);
                    prepareCommit(head, commit);
                    status = WriteStatus.STORE_PASS;
                    comment = "OK";
                    error = null;
                } catch (Throwable ex) {
                    status = WriteStatus.STORE_FAIL;
                    comment = ex.getMessage();
                    error = ex;
                    throw new RuntimeException(ex);
                } finally {
                    if (callback != null) {
                        callback.handle(commit, status, comment, error);
                    }
                    try {
                        reInitReadRepository();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
    }

    public VersionedResource getVersionedResource(final String resourceId) {
        try {
            String trimmedResourceId = VersionAPI.trimToProperResourceId(resourceId);
            if (StringUtils.isBlank(trimmedResourceId)) {
                throw new IllegalArgumentException("Invalid resource id!");
            }
            Set<ObjectId> revisionIds = getGraphForResourceId(trimmedResourceId);
            if (revisionIds == null || revisionIds.isEmpty()) {
                throw new IllegalArgumentException("Resource id doesn't exist!");
            }
            Revision[] revisions = new Revision[revisionIds.size()];
            int i = 0;
            for (ObjectId revisionId : revisionIds) {
                String revisionIdStr = ObjectId.toString(revisionId);
                String content = new String(readObject(revisionIdStr));
                revisions[i++] = VersionAPI.createRevision(VersionAPI.createResource(trimmedResourceId, content),
                        revisionIdStr);
            }
            return VersionAPI.createVersionedResource(Arrays.asList(revisions));
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }

    public Resource getResource(final String resourceId) {
        try {
            String trimmedResourceId = VersionAPI.trimToProperResourceId(resourceId);
            if (StringUtils.isBlank(trimmedResourceId)) {
                throw new IllegalArgumentException("Invalid resource id!");
            }
            ObjectId resourceObjectId;
            Tree head = getHeadTree(getReadRepository());
            if (!head.existsBlob(trimmedResourceId)) {
                throw new IllegalArgumentException("Resource id doesn't exist!");
            }
            TreeEntry treeEntry = head.findBlobMember(trimmedResourceId);
            resourceObjectId = treeEntry.getId();
            return VersionAPI.createResource(trimmedResourceId,
                    new String(readObject(ObjectId.toString(resourceObjectId))));
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }

    public Resource getResourceByRevision(final String revisionId, final String resourceId) {
        try {
            String trimmedResourceId = VersionAPI.trimToProperResourceId(resourceId);
            if (StringUtils.isBlank(trimmedResourceId)) {
                throw new IllegalArgumentException("Invalid resource id!");
            }
            ObjectId resourceObjectId;
            resourceObjectId = ObjectId.fromString(revisionId);
            return VersionAPI.createResource(trimmedResourceId,
                    new String(readObject(ObjectId.toString(resourceObjectId))));
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }

    public byte[] readObject(final String objectIdStr) throws IOException, IllegalArgumentException {
        if (StringUtils.isBlank(objectIdStr)) {
            throw new IllegalArgumentException("Invalid Object id!");
        }
        ObjectId objectId = ObjectId.fromString(objectIdStr);
        ObjectLoader objectLoader = getReadRepository().openObject(objectId);
        if (objectLoader.getType() != Constants.OBJ_BLOB) {
            throw new IllegalArgumentException("Not a blob: " + objectIdStr);
        }
        return objectLoader.getBytes();
    }

    public Collection<Commit> searchForCommits(final Collection<QueryParameter> parameters) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Collection<Revision> searchForRevisions(final Collection<QueryParameter> parameters) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Map<String, byte[]> readBlobObjects(final String... objectIds)
            throws IOException, IllegalArgumentException {
        checkInitialized();
        if (objectIds == null || objectIds.length <= 0) {
            throw new IllegalArgumentException("Empty Object IDs!");
        }
        Map<String, byte[]> blobs = new HashMap<String, byte[]>(objectIds.length);
        for (String objectIdStr : objectIds) {
            byte[] bytes = readObject(objectIdStr);
            blobs.put(objectIdStr, bytes);
        }
        return blobs;
    }

    protected Tree getHeadTree(Repository repository) throws IOException {
        Tree head;
        org.eclipse.jgit.lib.Commit headCommit = repository.mapCommit(Constants.HEAD);
        if (headCommit == null) {
            head = new Tree(repository);
        } else {
            head = headCommit.getTree();
        }
        return head;
    }

    protected ObjectId addOrUpdateToHead(final Commit commit, final Tree head) throws IOException {
        for (Revision revision : commit.getRevisions()) {
            String objectPath = revision.getResource().getId();
            FileTreeEntry treeEntry;
            boolean newEntry = false;
            if (head.existsBlob(objectPath)) {
                treeEntry = (FileTreeEntry) head.findBlobMember(objectPath);
            } else {
                treeEntry = head.addFile(objectPath);
                newEntry = true;
            }
            treeEntry.setExecutable(false);
            if (!revision.getResource().isDeleted()) {
                if (revision instanceof MutableRevision) {
                    ObjectId revisionId = getObjectWriter().writeBlob(revision.getResource().getContentSize(),
                            revision.getResource().getContentAsStream());
                    MutableRevision mutableRevision = (MutableRevision) revision;
                    mutableRevision.setRevisionId(ObjectId.toString(revisionId));
                    treeEntry.setId(revisionId);
                } else {
                    throw new IllegalArgumentException("SPI not implemented by API!");
                }
            } else if (!newEntry) {
                if (revision instanceof MutableRevision) {
                    ObjectId revisionId = treeEntry.getId();
                    MutableRevision mutableRevision = (MutableRevision) revision;
                    mutableRevision.setRevisionId(ObjectId.toString(revisionId));
                    treeEntry.delete();
                } else {
                    throw new IllegalArgumentException("SPI not implemented by API!");
                }
            }
        }
        GitIndex index = getWriteRepository().getIndex();
        index.readTree(head);
        index.write();
        ObjectId newHeadId = index.writeTree();
        head.setId(newHeadId);
        return newHeadId;
    }

    protected ObjectWriter getObjectWriter() {
        if (objectWriter == null) {
            objectWriter = new ObjectWriter(getWriteRepository());
        }
        return objectWriter;
    }

    protected void performCommit(final Commit newCommit, final Tree head) throws IOException {
        ObjectId[] parentIds;
        ObjectId currentHeadId = getWriteRepository().resolve(Constants.HEAD);
        if (currentHeadId != null) {
            parentIds = new ObjectId[] { currentHeadId };
        } else {
            parentIds = new ObjectId[0];
        }
        org.eclipse.jgit.lib.Commit commit = new org.eclipse.jgit.lib.Commit(getWriteRepository(), parentIds);
        commit.setTree(head);
        commit.setTreeId(head.getId());
        PersonIdent person = new PersonIdent(newCommit.getAuthor().getName(), newCommit.getAuthor().getEmail());
        commit.setAuthor(person);
        commit.setCommitter(person);
        commit.setMessage(newCommit.getCommitMessage());
        ObjectId newCommitId = getObjectWriter().writeCommit(commit);
        if (newCommit instanceof MutableCommit) {
            MutableCommit mutableCommit = (MutableCommit) newCommit;
            mutableCommit.setCommitId(ObjectId.toString(newCommitId));
            mutableCommit.setCommitTime(commit.getCommitter().getWhen());
            commit.setCommitId(newCommitId);
            if (commit.getParentIds().length > 0) {
                mutableCommit.setParentCommitId(ObjectId.toString(commit.getParentIds()[0]));
            } else {
                mutableCommit.setParentCommitId(ObjectId.toString(ObjectId.zeroId()));
            }
        } else {
            throw new IllegalArgumentException("SPI not implemented by API!");
        }
        RefUpdate refUpdate = getWriteRepository().updateRef(Constants.HEAD);
        refUpdate.setNewObjectId(commit.getCommitId());
        refUpdate.setRefLogMessage(commit.getMessage(), false);
        refUpdate.forceUpdate();
    }

    protected void prepareCommit(final Tree head, final Commit commit) throws IOException, IOException {
        ObjectId currentHeadId = getWriteRepository().resolve(Constants.HEAD);
        boolean commitAvailable = true;
        if (currentHeadId != null) {
            org.eclipse.jgit.lib.Commit headCommit = writeRepository.mapCommit(currentHeadId);
            if (headCommit != null) {
                Tree headTree = headCommit.getTree();
                if (headTree != null && head.getId().equals(headTree.getId())) {
                    commitAvailable = false;
                }
            }
        }
        if (commitAvailable || getConfig().isAllowNoChangeCommit()) {
            performCommit(commit, head);
        }
    }

    protected Set<ObjectId> getGraphForResourceId(String resourceId)
            throws MissingObjectException, IncorrectObjectTypeException, IOException {
        final Set<ObjectId> versions = new LinkedHashSet<ObjectId>();
        final RevWalk rw = new RevWalk(getReadRepository());
        final TreeWalk tw = new TreeWalk(getReadRepository());
        rw.markStart(rw.parseCommit(getReadRepository().resolve(Constants.HEAD)));
        tw.setFilter(TreeFilter.ANY_DIFF);
        RevCommit c;
        while ((c = rw.next()) != null) {
            final ObjectId[] p = new ObjectId[c.getParentCount() + 1];
            for (int i = 0; i < c.getParentCount(); i++) {
                rw.parseAny(c.getParent(i));
                p[i] = c.getParent(i).getTree();
            }
            final int me = p.length - 1;
            p[me] = c.getTree();
            tw.reset(p);
            while (tw.next()) {
                if (tw.getFileMode(me).getObjectType() == Constants.OBJ_BLOB) {
                    // This path was modified relative to the ancestor(s)
                    String s = tw.getPathString();
                    if (s != null && s.equals(resourceId)) {
                        versions.add(tw.getObjectId(me));
                    }
                }
                if (tw.isSubtree()) {
                    // make sure we recurse into modified directories
                    tw.enterSubtree();
                }
            }
        }
        return versions;
    }
}