com.gitblit.plugin.flowdock.TicketMessageGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.gitblit.plugin.flowdock.TicketMessageGenerator.java

Source

/*
 * Copyright 2014 gitblit.com.
 *
 * 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 com.gitblit.plugin.flowdock;

import java.io.IOException;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Change;
import com.gitblit.models.TicketModel.Patchset;
import com.gitblit.models.TicketModel.Review;
import com.gitblit.models.UserModel;
import com.gitblit.servlet.GitblitContext;
import com.gitblit.utils.ActivityUtils;
import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;

/**
 * This class generates a custom html payload.
 *
 * @author James Moger
 *
 */
public class TicketMessageGenerator extends TicketPayloadGenerator {

    final String name = getClass().getSimpleName();

    final Logger log = LoggerFactory.getLogger(getClass());

    final IStoredSettings settings;

    private final String addPattern = "<span style=\"color:darkgreen;\">+{0}</span>";

    private final String delPattern = "<span style=\"color:darkred;\">-{0}</span>";

    public TicketMessageGenerator() {
        super();

        IRuntimeManager runtimeManager = GitblitContext.getManager(IRuntimeManager.class);
        settings = runtimeManager.getSettings();
    }

    @Override
    public Payload generatePayload(TicketModel ticket) {

        String ticketUrl = getUrl(ticket);

        Set<TicketModel.Field> fieldExclusions = new HashSet<TicketModel.Field>();
        fieldExclusions.addAll(Arrays.asList(TicketModel.Field.watchers, TicketModel.Field.voters,
                TicketModel.Field.status, TicketModel.Field.mentions));

        Change change = ticket.changes.get(0);
        IUserManager userManager = GitblitContext.getManager(IUserManager.class);
        UserModel authorModel = userManager.getUserModel(change.author);
        String subject = getSubject(ticket, String.format("new %s ticket", ticket.type));

        StringBuilder sb = new StringBuilder();
        sb.append(String.format("<b>%s</b> has created <b>%s</b> <a href=\"%s\">ticket-%s</a>",
                authorModel.getDisplayName(), StringUtils.stripDotGit(ticket.repository), ticketUrl,
                ticket.number));

        fields(sb, ticket, ticket.changes.get(0), fieldExclusions);

        MessagePayload payload = new MessagePayload().from(authorModel).subject(subject).content(sb.toString())
                .project(getProject(ticket)).source(getSource(ticket)).tags(getTags(ticket)).link(ticketUrl);

        return payload;
    }

    @Override
    public Payload generatePayload(TicketModel ticket, Change change) {

        Set<TicketModel.Field> fieldExclusions = new HashSet<TicketModel.Field>();
        fieldExclusions.addAll(
                Arrays.asList(TicketModel.Field.watchers, TicketModel.Field.voters, TicketModel.Field.mentions,
                        TicketModel.Field.title, TicketModel.Field.body, TicketModel.Field.mergeSha));

        IUserManager userManager = GitblitContext.getManager(IUserManager.class);
        UserModel authorModel = userManager.getUserModel(change.author);
        String author = "<b>" + authorModel.getDisplayName() + "</b>";
        String url = String.format("<a href=\"%s\">ticket-%s</a>", getUrl(ticket), ticket.number);
        String repo = "<b>" + StringUtils.stripDotGit(ticket.repository) + "</b>";
        String subject = null;
        String msg = null;

        if (change.hasReview()) {
            /*
             * Patchset review
             */
            subject = getSubject(ticket,
                    String.format("reviewed patchset %s-%s", change.patchset.number, change.patchset.rev));
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("%s has reviewed %s %s patchset %s-%s", author, repo, url,
                    change.patchset.number, change.patchset.rev));
            sb.append("<p/>");

            Review review = change.review;
            String d = settings.getString(Keys.web.datestampShortFormat, "yyyy-MM-dd");
            String t = settings.getString(Keys.web.timeFormat, "HH:mm");
            DateFormat df = new SimpleDateFormat(d + " " + t);
            List<Change> reviews = ticket.getReviews(ticket.getPatchset(review.patchset, review.rev));
            sb.append(
                    "<table><thead<tr><th>Date</th><th>Reviewer</th><th>Score</th><th>Description</th></tr></thead><tbody>\n");
            for (Change c : reviews) {
                String name = c.author;
                UserModel u = userManager.getUserModel(change.author);
                if (u != null) {
                    name = u.getDisplayName();
                }
                String score;
                switch (change.review.score) {
                case approved:
                    score = MessageFormat.format(addPattern, c.review.score.getValue());
                    break;
                case vetoed:
                    score = MessageFormat.format(delPattern, Math.abs(c.review.score.getValue()));
                    break;
                default:
                    score = "" + c.review.score.getValue();
                }
                String date = df.format(c.date);
                sb.append(String.format("<tr><td>%1$s</td><td>%2$s</td><td>%3$s</td><td>%4$s</td></tr>\n", date,
                        name, score, c.review.score.toString()));
            }
            sb.append("</tbody></table>");
            msg = sb.toString();

        } else if (change.hasPatchset()) {
            /*
             * New Patchset
             */
            Patchset ps = change.patchset;
            String tip = ps.tip;
            String base;
            String leadIn;
            if (change.patchset.rev == 1) {
                if (change.patchset.number == 1) {
                    /*
                     * Initial proposal
                     */
                    subject = getSubject(ticket, "proposal pushed");
                    leadIn = String.format("%s has pushed a proposal for %s %s", author, repo, url);
                } else {
                    /*
                     * Rewritten patchset
                     */
                    subject = getSubject(ticket, String.format("patchset %s pushed (%s)", ps.number, ps.type));
                    leadIn = String.format("%s has rewritten the patchset for %s %s (%s)", author, repo, url,
                            ps.type);
                }
                base = change.patchset.base;
            } else {
                /*
                 * Fast-forward patchset update
                 */
                String noun = ps.added == 1 ? "commit" : "commits";
                subject = getSubject(ticket, String.format("added %s %s", ps.added, noun));
                leadIn = String.format("%s has added %s %s to %s %s", author, ps.added, noun, repo, url);
                Patchset prev = ticket.getPatchset(ps.number, ps.rev - 1);
                base = prev.tip;
            }

            StringBuilder sb = new StringBuilder();
            sb.append(leadIn);

            // show the fields above the commit list
            fields(sb, ticket, change, fieldExclusions);

            // abbreviated commit list
            List<RevCommit> commits = getCommits(ticket.repository, base, tip);
            sb.append("\n<table><tbody>\n");
            int shortIdLen = settings.getInteger(Keys.web.shortCommitIdLength, 6);
            int maxCommits = 5;
            for (int i = 0; i < Math.min(maxCommits, commits.size()); i++) {
                RevCommit commit = commits.get(i);
                String username = "";
                String email = "";
                if (commit.getAuthorIdent().getEmailAddress() != null) {
                    username = commit.getAuthorIdent().getName();
                    email = commit.getAuthorIdent().getEmailAddress().toLowerCase();
                    if (StringUtils.isEmpty(username)) {
                        username = email;
                    }
                } else {
                    username = commit.getAuthorIdent().getName();
                    email = username.toLowerCase();
                }
                String gravatarUrl = ActivityUtils.getGravatarThumbnailUrl(email, 16);
                String commitUrl = getUrl(ticket.repository, null, commit.getName());
                String shortId = commit.getName().substring(0, shortIdLen);
                String shortMessage = StringUtils.trimString(commit.getShortMessage(), Constants.LEN_SHORTLOG);
                String row = String.format(
                        "<tr><td><img src=\"%s\"/></td><td><pre><a href=\"%s\">%s</a></pre></td><td>%s</td></tr>\n",
                        gravatarUrl, commitUrl, shortId, shortMessage);
                sb.append(row);
            }
            sb.append("</tbody></table>\n");

            // compare link
            if (commits.size() > 1) {
                String compareUrl = getUrl(ticket.repository, base, tip);
                String compareText;
                if (commits.size() > maxCommits) {
                    int diff = commits.size() - maxCommits;
                    if (diff == 1) {
                        compareText = "1 more commit";
                    } else {
                        compareText = String.format("%d more commits", diff);
                    }
                } else {
                    compareText = String.format("view comparison of these %s commits", commits.size());
                }
                sb.append(String.format("<a href=\"%s\">%s</a>\n", compareUrl, compareText));
            }

            msg = sb.toString();

        } else if (change.isMerge()) {
            /*
             * Merged
             */
            subject = getSubject(ticket, String.format("merged to %s", ticket.mergeTo));
            msg = String.format("%s has merged %s %s to <b>%s</b>", author, repo, url, ticket.mergeTo);
        } else if (change.isStatusChange()) {
            /*
             * Status Change
             */
            subject = getSubject(ticket, String.format("status changed to %s", ticket.status));
            msg = String.format("%s has changed the status of %s %s", author, repo, url);
        } else if (change.hasComment() && settings.getBoolean(Plugin.SETTING_POST_TICKET_COMMENTS, true)) {
            /*
             * Comment
             */
            subject = getSubject(ticket, "comment added");
            msg = String.format("%s has commented on %s %s", author, repo, url);
        }

        if (msg == null) {
            // not a change we are reporting
            return null;
        }

        StringBuilder sb = new StringBuilder();
        sb.append(msg);

        // fields on patchset changes are output above this point
        if (!change.hasPatchset()) {
            fields(sb, ticket, change, fieldExclusions);
        }

        String ticketUrl = getUrl(ticket);

        MessagePayload payload = new MessagePayload().from(authorModel).subject(subject).content(sb.toString())
                .project(getProject(ticket)).source(getSource(ticket)).tags(getTags(ticket)).link(ticketUrl);

        return payload;
    }

    protected String getSubject(TicketModel ticket, String message) {
        return ticket.title;
    }

    protected void fields(StringBuilder sb, TicketModel ticket, Change change,
            Set<TicketModel.Field> fieldExclusions) {
        Map<TicketModel.Field, String> filtered = new HashMap<TicketModel.Field, String>();
        if (change.hasFieldChanges()) {
            for (Map.Entry<TicketModel.Field, String> fc : change.fields.entrySet()) {
                if (!fieldExclusions.contains(fc.getKey())) {
                    // field is included
                    filtered.put(fc.getKey(), fc.getValue());
                }
            }
        }

        if (change.hasComment() && settings.getBoolean(Plugin.SETTING_POST_TICKET_COMMENTS, true)) {
            // transform Markdown comment
            sb.append("<br/>\n");
            String comment = renderMarkdown(change.comment.text, ticket.repository);
            sb.append(comment);
        }

        // ensure we have some basic context fields
        if (!filtered.containsKey(TicketModel.Field.title)) {
            filtered.put(TicketModel.Field.title, ticket.title);
        }

        // sort by field ordinal
        List<TicketModel.Field> fields = new ArrayList<TicketModel.Field>(filtered.keySet());
        Collections.sort(fields);

        if (fields.size() > 0) {
            sb.append("\n<table class=\"property-list\"><tbody>\n");
            for (TicketModel.Field field : fields) {
                String value;
                if (filtered.get(field) == null) {
                    continue;
                } else {
                    value = filtered.get(field);

                    if (TicketModel.Field.body == field) {
                        // transform the body to html
                        value = renderMarkdown(value, ticket.repository);
                    } else if (TicketModel.Field.topic == field) {
                        // link bugtraq matches
                        value = renderBugtraq(value, ticket.repository);
                    } else if (TicketModel.Field.responsible == field) {
                        // lookup display name of the user
                        value = getDisplayName(value);
                    }
                }
                sb.append(String.format("<tr><td><b>%1$s:<b/></td><td>%2$s</td></tr>\n", field.name(), value));
            }
            sb.append("</tbody></table>\n");
        }
    }

    protected String renderMarkdown(String markdown, String repository) {
        if (StringUtils.isEmpty(markdown)) {
            return markdown;
        }

        // transform the body to html
        String bugtraq = renderBugtraq(markdown, repository);
        String html = MarkdownUtils.transformGFM(settings, bugtraq, repository);

        // strip paragraph tags
        html = html.replace("<p>", "");
        html = html.replace("</p>", "<br/><br/>");
        return html;
    }

    protected String renderBugtraq(String value, String repository) {
        if (StringUtils.isEmpty(value)) {
            return value;
        }

        IRepositoryManager repositoryManager = GitblitContext.getManager(IRepositoryManager.class);
        Repository db = repositoryManager.getRepository(repository);
        try {
            BugtraqProcessor bugtraq = new BugtraqProcessor(settings);
            value = bugtraq.processText(db, repository, value);
        } finally {
            db.close();
        }
        return value;
    }

    protected String getDisplayName(String username) {
        if (StringUtils.isEmpty(username)) {
            return username;
        }

        IUserManager userManager = GitblitContext.getManager(IUserManager.class);
        UserModel user = userManager.getUserModel(username);
        if (user != null) {
            String displayName = user.getDisplayName();
            if (!StringUtils.isEmpty(displayName) && !username.equals(displayName)) {
                return displayName;
            }
        }
        return username;
    }

    private List<RevCommit> getCommits(String repositoryName, String baseId, String tipId) {
        IRepositoryManager repositoryManager = GitblitContext.getManager(IRepositoryManager.class);
        Repository db = repositoryManager.getRepository(repositoryName);
        List<RevCommit> list = new ArrayList<RevCommit>();
        RevWalk walk = new RevWalk(db);
        walk.reset();
        walk.sort(RevSort.TOPO);
        walk.sort(RevSort.REVERSE, true);
        try {
            RevCommit tip = walk.parseCommit(db.resolve(tipId));
            RevCommit base = walk.parseCommit(db.resolve(baseId));
            walk.markStart(tip);
            walk.markUninteresting(base);
            for (;;) {
                RevCommit c = walk.next();
                if (c == null) {
                    break;
                }
                list.add(c);
            }
        } catch (IOException e) {
            // Should never happen, the core receive process would have
            // identified the missing object earlier before we got control.
            log.error("failed to get commits", e);
        } finally {
            walk.release();
            db.close();
        }
        return list;
    }
}