bear.plugins.misc.Releases.java Source code

Java tutorial

Introduction

Here is the source code for bear.plugins.misc.Releases.java

Source

/*
 * Copyright (C) 2013 Andrey Chaschev.
 *
 * 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 bear.plugins.misc;

import bear.context.HavingContext;
import bear.core.Bear;
import bear.core.SessionContext;
import bear.core.except.PermissionsException;
import bear.session.DynamicVariable;
import bear.session.Variables;
import bear.task.BearException;
import bear.vcs.*;
import chaschev.json.JacksonMapper;
import chaschev.util.Exceptions;
import com.bethecoder.table.ASCIITableHeader;
import com.bethecoder.table.AsciiTableInstance;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.*;

import static bear.task.TaskResult.okOrAbsent;
import static com.bethecoder.table.ASCIITableHeader.h;
import static com.google.common.base.Optional.*;

/**
 * Releases are saved as JSON cache in root releases folders.
 *
 * @author Andrey Chaschev chaschev@gmail.com
 */
public class Releases extends HavingContext<Releases, SessionContext> {
    private static final Logger logger = LoggerFactory.getLogger(Releases.class);

    static final JacksonMapper JACKSON_MAPPER = new JacksonMapper().prettyPrint(true);

    ReleasesPlugin releases;

    final DynamicVariable<Integer> retrieveLastXCommits = Variables.newVar(5);

    Bear bear;
    String current;

    transient TreeSet<String> folders;

    /**
     * Release is null in cache when there is no such release on the machine.
     */
    protected final Map<String, Release> folderMap = new TreeMap<String, Release>();
    private boolean loaded;
    private final ObjectMapper mapper;

    public Releases(SessionContext $, ReleasesPlugin releases) {
        super($);
        this.releases = releases;
        folders = new TreeSet<String>();
        mapper = new ObjectMapper();
    }

    public Release activatePending(PendingRelease pendingRelease) {
        logger.info("activating release {}", pendingRelease);

        checkLoaded();

        String releasePath = $(releases.releasePath);

        $.sys.move(pendingRelease.path).to(releasePath).run();

        removeItem(pendingRelease.path);

        Release activeRelease = new Release(pendingRelease.log, pendingRelease.branchInfo, releasePath, "active");

        folders.add(releasePath);
        folderMap.put(releasePath, activeRelease);

        makeActive(activeRelease);

        sort();

        switchLinkTo(releasePath);

        cleanupAndSave();

        //        saveJson();

        return activeRelease;
    }

    private void makeActive(Release activeRelease) {
        for (Release release : folderMap.values()) {
            if (release == null || "failed".equals(release.status))
                continue;
            release.status = "inactive";
        }

        activeRelease.status = "active";

        $.putConst(releases.activatedRelease, Optional.of(activeRelease));
    }

    public Optional<Release> getCurrentRelease() {
        checkLoaded();

        if (current == null) {
            return Optional.absent();
        }

        Optional<Release> currentRelease = findByRef(ReleaseRef.path(current));

        if (currentRelease.isPresent()) {
            makeActive(currentRelease.get());
        }

        return currentRelease;
    }

    public Optional<Release> getRelease(String folder) {
        checkLoaded();

        Optional<Release> optional = computeRelease(folder);

        if (!folderMap.containsKey(folder)) {
            folderMap.put(folder, optional.orNull());
        }

        if (!folders.contains(folder)) {
            folders.add(folder);
        }

        return fromNullable(folderMap.get(folder));
    }

    public Releases load() {
        loadCache();
        current = $.sys.readLink($(releases.currentReleaseLinkPath));
        loaded = true;

        //        if(logger.isDebugEnabled()){
        //            logger.debug("loaded releases:\n{}", show());
        //        }

        return this;
    }

    public String last() {
        checkLoaded();
        return folders.last();
    }

    public String previous() {
        Iterator<String> it = folders.descendingIterator();
        it.next();
        return it.next();
    }

    public PendingRelease newPendingRelease() {
        checkLoaded();

        return newPendingRelease(Optional.<BranchInfo>absent(), Optional.<VcsLogInfo>absent());
    }

    public PendingRelease newPendingRelease(@Nonnull Optional<BranchInfo> branchInfo,
            @Nonnull Optional<VcsLogInfo> vcsLogInfo) {
        checkLoaded();

        String pendingPath = $(releases.pendingReleasePath);

        CommandLineResult<?> result = $.sys.mkdirs(pendingPath).run();

        if (result.isErrorOf(PermissionsException.class)) {
            throw new BearException(
                    "releases plugin: unable to create a pending directory, did you run project.setup()?");
        }

        PendingRelease release;

        if (!branchInfo.isPresent() || !vcsLogInfo.isPresent()) {
            Optional<Release> temp = computeRelease(pendingPath);

            if (!temp.isPresent()) {
                throw new RuntimeException(
                        "error occurred while computing release: " + pendingPath + ". it might not exist");
            }

            release = new PendingRelease(temp.get().log, temp.get().branchInfo, temp.get().path, this);
        } else {
            release = new PendingRelease(vcsLogInfo, branchInfo, pendingPath, this);
        }

        folders.add(pendingPath);
        folderMap.put(pendingPath, release);

        sort();

        saveJson();

        $.putConst(releases.pendingRelease, release);
        $.putConst(releases.rollbackToRelease, getCurrentRelease());

        logger.info("newPendingRelease - RELEASES:\n{}", show());

        return release;
    }

    public void rollbackTo(Release release) {
        rollbackTo(ReleaseRef.path(release.path));
    }

    public void rollbackTo(ReleaseRef releaseRef) {
        checkLoaded();

        Optional<Release> release = findByRef(releaseRef);

        checkPresent(releaseRef, release);

        switchLinkTo(release.get().path);
    }

    public void deleteRelease(ReleaseRef releaseRef) {
        checkLoaded();

        Optional<Release> release = findByRef(releaseRef);

        checkPresent(releaseRef, release);

        String path = release.get().path;

        if (path.equals(current)) {
            throw new IllegalArgumentException("won't delete current release: " + releaseRef);
        }

        removeItem(path);

        $.sys.rm(path).run();

        saveJson();
    }

    public void cleanupAndSave() {
        checkLoaded();

        int keepX = $(releases.keepXReleases);

        if ($(releases.cleanPending)) {
            String path = $(releases.path) + "/" + $(releases.pendingName) + "*";
            $.sys.rm(path).sudo().run();
        }

        if (keepX > 0) {
            delete(listToDelete(keepX));
        }

        saveJson();
    }

    void delete(List<String> toDelete) {
        checkLoaded();

        Preconditions.checkArgument(!Iterables.contains(toDelete, current), "won't delete current folder!");

        for (String s : toDelete) {
            removeItem(s);
        }

        //apps may be run under a root and create files with root's owner
        String[] paths = toDelete.toArray(new String[toDelete.size()]);

        $.sys.rm(paths).sudo().run();

        sort();
    }

    public void invalidateCache() {
        //        checkLoaded();

        folderMap.clear();
    }

    private void checkLoaded() {
        Preconditions.checkArgument(loaded, "you need to call load() to load the data");
    }

    private static void checkPresent(ReleaseRef releaseRef, Optional<Release> release) {
        if (!release.isPresent()) {
            throw new IllegalArgumentException("no such release: " + releaseRef);
        }
    }

    private void switchLinkTo(String path) {
        current = path;

        $.sys.link($(releases.currentReleaseLinkPath)).toSource(path).run();

        getCurrentRelease(); //makes it active
    }

    public Optional<Release> findAny(String labelOrPath) {
        return findByRef(ReleaseRef.label(labelOrPath)).or(findByRef(ReleaseRef.path(labelOrPath)));
    }

    public Optional<Release> findByRef(ReleaseRef releaseRef) {
        String path;

        if (releaseRef.isLabel()) {
            path = $(releases.path) + "/" + releaseRef.label;
        } else {
            path = releaseRef.path;
        }

        return getRelease(path);
    }

    private void removeItem(String s) {
        folderMap.remove(s);
        folders.remove(s);
    }

    void saveJson() {
        try {
            $.sys.writeString(JACKSON_MAPPER.toJSON(folderMap)).toPath($(releases.releasesJsonPath)).run();
        } catch (Exception e) {
            logger.warn("unable to save JSON", e);
        }
    }

    protected Releases loadCache() {
        folders.addAll(ls());

        try {
            Map<String, Release> map = loadMap();

            Iterator<String> it;

            it = folders.iterator();

            while (it.hasNext()) {
                String next = it.next();
                if (next.endsWith("/current")) {
                    it.remove();
                    break;
                }
            }

            folderMap.putAll(map);

            for (it = folderMap.keySet().iterator(); it.hasNext();) {
                String s = it.next();
                if (!folders.contains(s)) {
                    logger.debug("removing missing item from cache: {}", s);
                    it.remove();
                }
            }

            sort();

            return this;
        } catch (Exception e) {
            logger.warn("error during loading the cache", e);
            invalidateCache();
            return this;
        }
    }

    protected Map<String, Release> loadMap() {
        try {
            String json = $.sys.readString($(releases.releasesJsonPath), null);
            if (json == null)
                return new LinkedHashMap<String, Release>();
            return mapper.readValue(json, new TypeReference<Map<String, Release>>() {
            });
        } catch (IOException e) {
            throw Exceptions.runtime(e);
        }
        //        return JACKSON_MAPPER.fromJSON(, Map.class);
    }

    List<String> ls() {
        return $.sys.ls($(releases.path)).absolutePaths().run().getLines();
    }

    Optional<Release> computeRelease(String folder) {
        if (!$.sys.exists(folder)) {
            return absent();
        }

        if (!$.getGlobal().pluginOfInstance(VcsCLIPlugin.class).isPresent()) {
            return of(new Release(Optional.<VcsLogInfo>absent(), Optional.<BranchInfo>absent(), folder, null));
        }

        VCSSession vcs = $(bear.vcs);

        VcsLogInfo vcsLogInfo = vcs.logLastN($(retrieveLastXCommits)).run();

        BranchInfo branchInfo = vcs.queryRevision($(bear.revision)).run();

        return of(new Release(okOrAbsent(vcsLogInfo), okOrAbsent(branchInfo), folder, null));
    }

    List<String> listToDelete(int keepX) {
        if (folders.size() <= keepX)
            return Collections.emptyList();

        int toIndex = folders.size() - keepX;

        List<String> folders = new ArrayList<String>(this.folders);

        List<String> toDelete = new ArrayList<String>(folders.subList(0, toIndex));

        if (toDelete.contains(current)) {
            toDelete.remove(current);
            if (toIndex < folders.size()) {
                toDelete.add(folders.get(toIndex));
            }
        }

        return toDelete;
    }

    private void sort() {

    }

    public String show() {
        getCurrentRelease();

        Collection<Release> values = folderMap.values();

        List<String[]> table = new ArrayList<String[]>(values.size());

        for (Release release : values) {
            if (release == null)
                continue;

            table.add(new String[] { release.name(), release.getLastAuthor(),
                    release.branchInfo.isPresent() ? release.branchInfo.get().revision : "",
                    release.log.isPresent() ? release.log.get().firstComment().replace("\n", " ") : "",
                    release.status });
        }

        return AsciiTableInstance.get()
                .getTable(
                        new ASCIITableHeader[] { h("Name"), h("Author"), h("Revision").maxWidth(10),
                                h("Comment").maxWidth(50), h("Status") },
                        table.toArray(new String[table.size()][]));
    }

    public void markRollback(Release release) {
        mark(release, "rollback");
    }

    public void markFailed(Release release) {
        mark(release, "failed");
    }

    private void mark(Release release, String status) {
        release.status = status;
        saveJson();
    }
}