org.jboss.set.aphrodite.issue.trackers.jira.IssueWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.set.aphrodite.issue.trackers.jira.IssueWrapper.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright (c) 2016, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.set.aphrodite.issue.trackers.jira;

import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.BROWSE_ISSUE_PATH;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.DEV_ACK;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.FLAG_MAP;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.JSON_CUSTOM_FIELD;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.PM_ACK;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.QE_ACK;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.TARGET_RELEASE;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.getAphroditePriority;
import static org.jboss.set.aphrodite.issue.trackers.jira.JiraFields.getAphroditeStatus;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.atlassian.jira.rest.client.api.domain.ChangelogGroup;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.jboss.set.aphrodite.common.Utils;
import org.jboss.set.aphrodite.domain.Comment;
import org.jboss.set.aphrodite.domain.Flag;
import org.jboss.set.aphrodite.domain.FlagStatus;
import org.jboss.set.aphrodite.domain.Issue;
import org.jboss.set.aphrodite.domain.IssueEstimation;
import org.jboss.set.aphrodite.domain.IssueType;
import org.jboss.set.aphrodite.domain.Release;
import org.jboss.set.aphrodite.domain.Stage;
import org.jboss.set.aphrodite.domain.User;
import org.jboss.set.aphrodite.spi.NotFoundException;

import com.atlassian.jira.rest.client.api.domain.ChangelogItem;
import com.atlassian.jira.rest.client.api.domain.BasicComponent;
import com.atlassian.jira.rest.client.api.domain.BasicProject;
import com.atlassian.jira.rest.client.api.domain.IssueField;
import com.atlassian.jira.rest.client.api.domain.IssueFieldId;
import com.atlassian.jira.rest.client.api.domain.IssueLinkType.Direction;
import com.atlassian.jira.rest.client.api.domain.Project;
import com.atlassian.jira.rest.client.api.domain.TimeTracking;
import com.atlassian.jira.rest.client.api.domain.Version;
import com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;
import com.atlassian.jira.rest.client.api.domain.input.FieldInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;

/**
 * @author Ryan Emerson
 */
class IssueWrapper {

    private static final Log LOG = LogFactory.getLog(JiraIssueTracker.class);

    Issue jiraSearchIssueToIssue(URL baseURL, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        URL url = trackerIdToBrowsableUrl(baseURL, jiraIssue.getKey());
        return jiraIssueToIssue(url, jiraIssue);
    }

    private void setCreationTime(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        issue.setCreationTime(jiraIssue.getCreationDate().toDate());
    }

    private void setLastUpdated(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        issue.setLastUpdated(jiraIssue.getUpdateDate().toDate());
    }

    Issue jiraIssueToIssue(URL url, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        JiraIssue issue = new JiraIssue(url);
        copy(url, jiraIssue, issue);
        return issue;
    }

    void copy(final URL url, final com.atlassian.jira.rest.client.api.domain.Issue jiraIssue,
            final JiraIssue issue) {
        issue.setTrackerId(jiraIssue.getKey());
        issue.setSummary(jiraIssue.getSummary());
        issue.setDescription(jiraIssue.getDescription());
        issue.setStatus(getAphroditeStatus(jiraIssue.getStatus().getName()));
        issue.setPriority(getAphroditePriority(jiraIssue.getPriority().getName()));

        TimeTracking timeTracking = jiraIssue.getTimeTracking();
        if (timeTracking != null) {
            int estimate = (timeTracking.getOriginalEstimateMinutes() == null) ? 0
                    : timeTracking.getOriginalEstimateMinutes();
            int spent = (timeTracking.getTimeSpentMinutes() == null) ? 0 : timeTracking.getTimeSpentMinutes();
            issue.setEstimation(new IssueEstimation(estimate / 60d, spent / 60d));
        }

        setIssueStream(issue, jiraIssue);
        setIssueProject(issue, jiraIssue);
        setIssueComponent(issue, jiraIssue);

        setIssueUser((i, u) -> i.setAssignee(new User(u.getEmailAddress(), u.getName())), issue,
                jiraIssue.getAssignee());
        setIssueUser((i, u) -> i.setReporter(new User(u.getEmailAddress(), u.getName())), issue,
                jiraIssue.getReporter());

        setIssueStage(issue, jiraIssue);
        setIssueType(issue, jiraIssue);
        setIssueAffectedVersions(issue, jiraIssue);
        setIssueReleases(issue, jiraIssue);
        setIssueDependencies(url, issue, jiraIssue.getIssueLinks());
        setIssueComments(issue, jiraIssue);
        setCreationTime(issue, jiraIssue);
        setLastUpdated(issue, jiraIssue);
        // Set JIRA specific fields
        setPullRequests(issue, jiraIssue);
        setIssueSprintRelease(issue, jiraIssue);
        setLabels(issue, jiraIssue);
        setChangelog(issue, jiraIssue);
        setResolution(issue, jiraIssue);
    }

    private void setLabels(JiraIssue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        Set<String> jiraLabels = jiraIssue.getLabels();
        List<JiraLabel> labels = new ArrayList<>();

        if (jiraLabels != null)
            jiraLabels.forEach(name -> labels.add(new JiraLabel(name)));

        issue.setLabels(labels);
    }

    private void setChangelog(JiraIssue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        List<JiraChangelogGroup> changelog = createJiraChangelogGroups(jiraIssue);
        issue.setChangelog(changelog);
    }

    private List<JiraChangelogGroup> createJiraChangelogGroups(
            com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        List<JiraChangelogGroup> changelog = new ArrayList<>();
        if (jiraIssue.getChangelog() != null) {
            jiraIssue.getChangelog()
                    .forEach(changelogGroup -> changelog.add(createJiraChangelogGroup(changelogGroup)));
        }
        return changelog;
    }

    private JiraChangelogGroup createJiraChangelogGroup(ChangelogGroup changelogGroup) {
        final String noAuthor = "";
        String author = (changelogGroup.getAuthor() != null) ? changelogGroup.getAuthor().getName() : noAuthor;
        Date dateCreated = (changelogGroup.getCreated() != null) ? changelogGroup.getCreated().toDate()
                : new Date();
        List<JiraChangelogItem> changelogItems = createJiraChangelogItems(changelogGroup.getItems());
        return new JiraChangelogGroup(User.createWithUsername(author), dateCreated, changelogItems);
    }

    private List<JiraChangelogItem> createJiraChangelogItems(Iterable<ChangelogItem> changelogItems) {
        List<JiraChangelogItem> jiraChangelogItems = new ArrayList<>();
        if (changelogItems != null)
            changelogItems.forEach(item -> jiraChangelogItems.add(new JiraChangelogItem(item.getField(),
                    item.getFrom(), item.getFromString(), item.getTo(), item.getToString())));
        return jiraChangelogItems;
    }

    private void setResolution(JiraIssue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        if (jiraIssue.getResolution() != null && !"".equals(jiraIssue.getResolution())) {
            if (!JiraIssueResolution.hasId(jiraIssue.getResolution().getId())) {
                String msg = String.format("Could not convert issue resolution: %1$s (%2$s) for issue: %3$s",
                        jiraIssue.getResolution().getName(), jiraIssue.getResolution().getId(), jiraIssue.getKey());
                Utils.logWarnMessage(LOG, msg);
            } else
                issue.setResolution(JiraIssueResolution.getById(jiraIssue.getResolution().getId()));
        } else
            issue.setResolution(JiraIssueResolution.UNRESOLVED);

    }

    private static void setIssueAffectedVersions(JiraIssue issue,
            com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        if (jiraIssue.getAffectedVersions() != null) {
            List<String> affectedVersion = new ArrayList<String>(0);
            for (Version version : jiraIssue.getAffectedVersions())
                affectedVersion.add(version.getName());
            issue.setAffectedVersions(affectedVersion);
        }
    }

    // TODO find a solution for updating time estimates, see https://github.com/jboss-set/aphrodite/issues/23
    IssueInput issueToFluentUpdate(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue,
            Project project) throws NotFoundException {
        checkUnsupportedUpdateFields(issue);
        IssueInputBuilder inputBuilder = new IssueInputBuilder(jiraIssue.getProject().getKey(),
                jiraIssue.getIssueType().getId());

        issue.getSummary().ifPresent(inputBuilder::setSummary);
        inputBuilder.setFieldInput(new FieldInput(IssueFieldId.COMPONENTS_FIELD, issue.getComponents().stream()
                .map(e -> ComplexIssueInputFieldValue.with("name", e)).collect(Collectors.toList())));
        issue.getDescription().ifPresent(inputBuilder::setDescription);

        issue.getAssignee().ifPresent(assignee -> inputBuilder.setFieldInput(new FieldInput(
                IssueFieldId.ASSIGNEE_FIELD,
                ComplexIssueInputFieldValue.with("name", assignee.getName().orElseThrow(this::nullUsername)))));

        // this is ok but does nothing if there is no permissions.
        issue.getStage().getStateMap().entrySet().stream().filter(entry -> entry.getValue() != FlagStatus.NO_SET)
                .forEach(entry -> inputBuilder.setFieldInput(new FieldInput(
                        JSON_CUSTOM_FIELD + FLAG_MAP.get(entry.getKey()), entry.getValue().getSymbol())));

        Map<String, Version> versionsMap = StreamSupport.stream(project.getVersions().spliterator(), false)
                .collect(Collectors.toMap(Version::getName, Function.identity()));
        updateFixVersions(issue, versionsMap, inputBuilder);
        updateStreamStatus(issue, jiraIssue, versionsMap, inputBuilder);

        return inputBuilder.build();
    }

    private void updateStreamStatus(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue,
            Map<String, Version> versionsMap, IssueInputBuilder inputBuilder) throws NotFoundException {
        String customField = JSON_CUSTOM_FIELD + TARGET_RELEASE;
        IssueField issueField = jiraIssue.getField(customField);
        if (issueField == null || (issueField.getType() == null && issueField.getValue() == null)) {
            String msg = String.format(
                    "Unable to set a stream status for issue %1$s as %2$s projects do not utilise field: %3$s",
                    jiraIssue.getKey(), jiraIssue.getProject().getName(), customField);
            Utils.logWarnMessage(LOG, msg);
            return;
        }

        for (Map.Entry<String, FlagStatus> entry : issue.getStreamStatus().entrySet()) {
            if (entry.getValue() != FlagStatus.ACCEPTED) {
                String streamName = entry.getKey();
                Version version = versionsMap.get(streamName);
                if (version != null) {
                    inputBuilder.setFieldInput(new FieldInput(customField,
                            ComplexIssueInputFieldValue.with("value", version.getId())));
                } else {
                    throw new NotFoundException("No Stream exists for this project with the name : " + streamName);
                }
            }
        }
    }

    private void updateFixVersions(Issue issue, Map<String, Version> versionsMap, IssueInputBuilder inputBuilder)
            throws NotFoundException {
        List<Version> projectVersions = new ArrayList<>();
        for (Release release : issue.getReleases()) {
            String releaseName = release.getVersion().orElse(null);
            Version version = versionsMap.get(releaseName);
            if (version != null) {
                projectVersions.add(version);
            } else {
                throw new NotFoundException("No Release exists for this project with name : " + releaseName);
            }
        }
        inputBuilder.setFixVersions(projectVersions);
    }

    private void checkUnsupportedUpdateFields(Issue issue) {
        if (issue.getReporter().isPresent() && LOG.isDebugEnabled())
            LOG.debug("JIRA does not support updating the reporter field, field ignored.");
    }

    private void setIssueProject(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        BasicProject project = jiraIssue.getProject();
        if (project != null)
            issue.setProduct(project.getName());
    }

    private void setIssueComponent(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        Iterable<BasicComponent> components = jiraIssue.getComponents();
        List<String> tmp = new ArrayList<>();
        for (BasicComponent component : components) {
            tmp.add(component.getName());
        }
        issue.setComponents(tmp);
    }

    private IllegalArgumentException nullUsername() {
        throw new IllegalArgumentException(
                "JIRA issues require a non-null username in order to set an assignee/reporter");
    }

    private void setIssueUser(BiConsumer<Issue, com.atlassian.jira.rest.client.api.domain.User> function,
            Issue issue, com.atlassian.jira.rest.client.api.domain.User user) {
        if (user != null && user.getName() != null && user.getEmailAddress() != null)
            function.accept(issue, user);
    }

    private void setIssueStage(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        Stage stage = new Stage();
        setFlag(jiraIssue, stage, Flag.PM, JSON_CUSTOM_FIELD + PM_ACK);
        setFlag(jiraIssue, stage, Flag.DEV, JSON_CUSTOM_FIELD + DEV_ACK);
        setFlag(jiraIssue, stage, Flag.QE, JSON_CUSTOM_FIELD + QE_ACK);
        issue.setStage(stage);
    }

    private void setFlag(com.atlassian.jira.rest.client.api.domain.Issue jiraIssue, Stage stage, Flag flag,
            String fieldname) {
        if (jiraIssue.getField(fieldname) != null && jiraIssue.getField(fieldname).getValue() != null)
            stage.setStatus(flag, FlagStatus.getMatchingFlag((String) jiraIssue.getField(fieldname).getValue()));
        else {
            stage.setStatus(flag, FlagStatus.NO_SET);
        }
    }

    private void setIssueType(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        String type = jiraIssue.getIssueType().getName();
        issue.setType(getIssueType(type));
    }

    private IssueType getIssueType(String type) {
        type = type.trim().replaceAll(" +", " ");
        switch (type) {
        case "SUPPORT PATCH":
            // Counter-intuitave as you would think this would be SUPPORT_PATCH,
            // but this makes sense based upon JIRA description. See <jira domain>/rest/api/2/issuetype
            return IssueType.ONE_OFF;
        case "PATCH":
            return IssueType.SUPPORT_PATCH;
        default:
            return IssueType.getMatchingIssueType(type);
        }
    }

    private void setIssueSprintRelease(JiraIssue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        final IssueField issueField = jiraIssue.getFieldByName("Sprint");
        if (issueField != null && issueField.getValue() != null) {
            JSONArray fieldValue = (JSONArray) issueField.getValue();
            if (fieldValue.length() > 0)
                issue.setSprintRelease(extractSprintName(fieldValue));
        }
    }

    /*
     * Horrible hack :( - but no other options apparently
     * See https://answers.atlassian.com/questions/92681/how-to-get-sprints-using-greenhopper-api
    */
    private static final String NAME_FIELD_ATTRIBUTE = "name=";

    private String extractSprintName(JSONArray fieldValue) {
        try {
            String value = (String) fieldValue.get(0);
            value = value.substring(value.indexOf(NAME_FIELD_ATTRIBUTE));
            return value.substring(NAME_FIELD_ATTRIBUTE.length(), value.indexOf(','));
        } catch (JSONException e) {
            return "";
        }

    }

    private void setIssueStream(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        try {
            IssueField jsonField = jiraIssue.getField(JSON_CUSTOM_FIELD + TARGET_RELEASE);
            if (jsonField == null || jsonField.getValue() == null) {
                return;
            }
            JSONObject value = (JSONObject) jsonField.getValue();
            Map<String, FlagStatus> streamStatus;
            streamStatus = Collections.singletonMap(value.getString("name"), FlagStatus.ACCEPTED);
            issue.setStreamStatus(streamStatus);
        } catch (JSONException e) {
            LOG.error("error setting the stream in " + jiraIssue.getKey(), e);
        }

    }

    private void setIssueReleases(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        Iterable<Version> versions = jiraIssue.getFixVersions();
        if (versions != null) {
            List<Release> releases = StreamSupport.stream(jiraIssue.getFixVersions().spliterator(), false)
                    .map(version -> new Release(version.getName())).collect(Collectors.toList());
            issue.setReleases(releases);
        }
    }

    private void setIssueDependencies(URL originalUrl, Issue issue,
            Iterable<com.atlassian.jira.rest.client.api.domain.IssueLink> links) {
        if (links == null)
            return;
        final String INCORPORATES = "incorporates";

        for (com.atlassian.jira.rest.client.api.domain.IssueLink il : links) {
            // Add links of cloned to/from issues to the issue
            if (il.getIssueLinkType().getDescription().contains("cloned")) {
                URL url = trackerIdToBrowsableUrl(originalUrl, il.getTargetIssueKey());
                ((JiraIssue) issue).getLinkedCloneIssues().add(url);
            }

            // Add links of incorporates issues
            if (il.getIssueLinkType().getDescription().equals(INCORPORATES)) {
                URL url = trackerIdToBrowsableUrl(originalUrl, il.getTargetIssueKey());
                ((JiraIssue) issue).getLinkedIncorporatesIssues().add(url);
            }

            if (il.getIssueLinkType().getDirection().equals(Direction.INBOUND)) {
                URL url = trackerIdToBrowsableUrl(originalUrl, il.getTargetIssueKey());
                issue.getBlocks().add(url);
            } else {
                URL url = trackerIdToBrowsableUrl(originalUrl, il.getTargetIssueKey());
                issue.getDependsOn().add(url);
            }
        }
    }

    private void setIssueComments(Issue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        List<Comment> comments = new ArrayList<>();
        jiraIssue.getComments().forEach(c -> comments
                .add(new Comment(issue.getTrackerId().get(), Long.toString(c.getId()), c.getBody(), false)));
        issue.getComments().addAll(comments);
    }

    private void setPullRequests(JiraIssue issue, com.atlassian.jira.rest.client.api.domain.Issue jiraIssue) {
        IssueField fieldContent = jiraIssue.getFieldByName("Git Pull Request");//Git Pull Request
        if (fieldContent != null) {
            extractPullRequests(issue, (JSONArray) fieldContent.getValue());
        }
    }

    private static void extractPullRequests(JiraIssue issue, JSONArray urls) {
        if (urls != null && urls.length() > 0) {
            List<URL> prUrls = new ArrayList<URL>(urls.length());
            for (int index = 0; index < urls.length(); index++)
                prUrls.add(Utils.createURL(getFromJSONArray(index, urls).toString()));
            issue.setPullRequests(prUrls);
        }
    }

    private static Object getFromJSONArray(int i, JSONArray urls) {
        try {
            return urls.get(i);
        } catch (JSONException e) {
            throw new IllegalStateException(e);
        }
    }

    private URL trackerIdToBrowsableUrl(URL url, String trackerId) {
        try {
            String link = url.getProtocol() + "://" + url.getHost() + BROWSE_ISSUE_PATH + trackerId;
            return new URL(link);
        } catch (MalformedURLException e) {
            return null;
        }
    }
}