1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.issuetracker.jira;
 24 
 25 import org.openjdk.skara.issuetracker.*;
 26 import org.openjdk.skara.json.*;
 27 import org.openjdk.skara.network.*;
 28 
 29 import java.net.URI;
 30 import java.util.*;
 31 
 32 public class JiraProject implements IssueProject {
 33     private final JiraHost jiraHost;
 34     private final String projectName;
 35     private final RestRequest request;
 36 
 37     private JSONObject projectMetadataCache = null;
 38 
 39     JiraProject(JiraHost host, RestRequest request, String projectName) {
 40         this.jiraHost = host;
 41         this.projectName = projectName;
 42         this.request = request;
 43     }
 44 
 45     private JSONObject project() {
 46         if (projectMetadataCache == null) {
 47             projectMetadataCache = request.get("project/" + projectName).execute().asObject();
 48         }
 49         return projectMetadataCache;
 50     }
 51 
 52     private Map<String, String> issueTypes() {
 53         var ret = new HashMap<String, String>();
 54         for (var type : project().get("issueTypes").asArray()) {
 55             ret.put(type.get("name").asString(), type.get("id").asString());
 56         }
 57         return ret;
 58     }
 59 
 60     private Map<String, String> components() {
 61         var ret = new HashMap<String, String>();
 62         for (var type : project().get("components").asArray()) {
 63             ret.put(type.get("name").asString(), type.get("id").asString());
 64         }
 65         return ret;
 66     }
 67 
 68     private String projectId() {
 69         return project().get("id").asString();
 70     }
 71 
 72     private String defaultIssueType() {
 73         return issueTypes().values().stream()
 74                            .min(Comparator.naturalOrder()).orElseThrow();
 75     }
 76 
 77     private String defaultComponent() {
 78         return components().values().stream()
 79                            .min(Comparator.naturalOrder()).orElseThrow();
 80     }
 81 
 82     @Override
 83     public IssueTracker issueTracker() {
 84         return jiraHost;
 85     }
 86 
 87     @Override
 88     public URI webUrl() {
 89         return URIBuilder.base(jiraHost.getUri()).setPath("/projects/" + projectName).build();
 90     }
 91 
 92     @Override
 93     public Issue createIssue(String title, List<String> body) {
 94         var query = JSON.object()
 95                         .put("fields", JSON.object()
 96                                            .put("project", JSON.object()
 97                                                                .put("id", projectId()))
 98                                            .put("issuetype", JSON.object()
 99                                                                  .put("id", defaultIssueType()))
100                                            .put("components", JSON.array()
101                                                                   .add(JSON.object().put("id", defaultComponent())))
102                                            .put("summary", title)
103                                            .put("description", String.join("\n", body)));
104 
105         var data = request.post("issue")
106                           .body(query)
107                           .execute();
108 
109         return issue(data.get("key").asString()).orElseThrow();
110     }
111 
112     @Override
113     public Optional<Issue> issue(String id) {
114         if (id.indexOf('-') < 0) {
115             id = projectName.toUpperCase() + "-" + id;
116         }
117         var issueRequest = request.restrict("issue/" + id);
118         var issue = issueRequest.get("")
119                            .onError(r -> r.statusCode() == 404 ? JSON.object().put("NOT_FOUND", true) : null)
120                            .execute();
121         if (!issue.contains("NOT_FOUND")) {
122             return Optional.of(new JiraIssue(this, issueRequest, issue));
123         } else {
124             return Optional.empty();
125         }
126     }
127 
128     @Override
129     public List<Issue> issues() {
130         var ret = new ArrayList<Issue>();
131         var issues = request.post("search")
132                             .body("jql", "project = " + projectName + " AND status in (Open, New)")
133                             .execute();
134         for (var issue : issues.get("issues").asArray()) {
135             ret.add(new JiraIssue(this, request, issue));
136         }
137         return ret;
138     }
139 }