package com.thinkbiganalytics.jira; /*- * #%L * thinkbig-jira-rest-client * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Collections2; import com.thinkbiganalytics.jira.domain.CreateIssue; import com.thinkbiganalytics.jira.domain.CreateMeta; import com.thinkbiganalytics.jira.domain.GetIssue; import com.thinkbiganalytics.jira.domain.Issue; import com.thinkbiganalytics.jira.domain.IssueBuilder; import com.thinkbiganalytics.jira.domain.IssueType; import com.thinkbiganalytics.jira.domain.Project; import com.thinkbiganalytics.jira.domain.ServerInfo; import com.thinkbiganalytics.jira.domain.User; import com.thinkbiganalytics.rest.JerseyRestClient; import com.thinkbiganalytics.rest.JodaTimeMapperProvider; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; /** */ public class JiraJerseyClient extends JerseyRestClient implements JiraClient { LoadingCache<String, List<String>> issueTypeNameCache; private String apiPath = "/rest/api/latest/"; public JiraJerseyClient(JiraRestClientConfig config) { super(config); this.apiPath = config.getApiPath(); // cache the Issue Types related to a project since they will not change much. // But if they do, expire the cache after a certain amount of time this.issueTypeNameCache = CacheBuilder.newBuilder() .expireAfterAccess(20, TimeUnit.MINUTES) .build( new CacheLoader<String, List<String>>() { public List<String> load(String key) { try { return loadIssueTypeNamesForProject(key); } catch (JiraException e) { return null; } } }); //regiser the JoadTime mapper if (client != null) { client.register(JodaTimeMapperProvider.class); } } protected WebTarget getBaseTarget() { WebTarget target = super.getBaseTarget(); return target.path(apiPath); } public Issue getIssue(String key) throws JiraException { try { GetIssue getIssue = get("issue/" + key, null, GetIssue.class); Issue issue = new Issue(getIssue); return issue; } catch (Exception e) { throw new JiraException("Error getting Issue: " + key, e); } } /*** * Return the JIRA User that is allowed to be assigned issues for a given Project and UserName * * @param projectKey * @param username * @return * @ */ public User getAssignableUser(String projectKey, String username) { Map<String, Object> params = new HashMap<String, Object>(); params.put("project", projectKey); params.put("username", username); List<User> users = get("/user/assignable/search", params, new GenericType<List<User>>() { }); if (users != null && !users.isEmpty()) { return users.get(0); } return null; } /** * Check to see if a user is allowed to be assigned issues for a given project */ public boolean isAssignable(String projectKey, String username) { User user = null; try { user = getAssignableUser(projectKey, username); } catch (Exception e) { } return user != null; } /** * Return the CreateMeta Schema that needs to used to create new Jira Issues */ public CreateMeta getCreateMetadata(String projectKey) throws JiraException { try { Map<String, Object> params = new HashMap<String, Object>(); params.put("projectKeys", projectKey); params.put("expand", "projects.issuetypes.fields"); CreateMeta createData = get("/issue/createmeta", params, CreateMeta.class); return createData; } catch (Exception e) { throw new JiraException("Error getting Create Metadata for Project " + projectKey, e); } } /** * Return the list of valid IssueTypes for a given Jira Project */ public List<IssueType> getIssueTypesForProject(String projectKey) throws JiraException { CreateMeta createData = getCreateMetadata(projectKey); Project project = createData.getProject(); if (project != null) { return project.getIssueTypes(); } else { return null; } } /** * Return the List of Issue Type Names for a given Project */ private List<String> loadIssueTypeNamesForProject(String projectKey) throws JiraException { List<IssueType> issueTypes = null; issueTypes = getIssueTypesForProject(projectKey); List<String> names = new ArrayList<>(); if (issueTypes != null) { for (IssueType issueType : issueTypes) { names.add(issueType.getName()); } } return names; } /** * return the List of String names of the IssueTypes for a given Project */ public List<String> getIssueTypeNamesForProject(String projectKey) { return issueTypeNameCache.getUnchecked(projectKey); } /** * Check to see if a given issue type is valid for a Project */ public boolean isValidIssueType(String projectKey, final String issueTypeName) { List<String> issueTypes = getIssueTypeNamesForProject(projectKey); return isValidIssueType(issueTypes, issueTypeName); } /** * check gto see if the issueType List has the passed in issueTypeName */ public boolean isValidIssueType(List<String> issueTypes, final String issueTypeName) { if (issueTypes != null) { Predicate<String> matchesProject = new Predicate<String>() { @Override public boolean apply(String issueType) { return issueType.equals(issueTypeName); } }; Collection<String> matchingTypes = Collections2.filter(issueTypes, matchesProject); return matchingTypes != null && !matchingTypes.isEmpty(); } return false; } /** * Create a new Jira Issue */ public Issue createIssue(Issue issue) throws JiraException { String projectKey = issue.getProject() != null ? issue.getProject().getKey() : null; String issueType = issue.getIssueType() != null ? issue.getIssueType().getName() : null; String summary = issue.getSummary(); String description = issue.getDescription(); String assigneeName = issue.getAssignee() != null ? issue.getAssignee().getName() : null; //Validate the parameters List<String> issueTypes = getIssueTypeNamesForProject(projectKey); //Validate the Project Name if (issueTypes == null) { throw new JiraException("Unable to Create Issue: Project " + projectKey + " does not exist. Issue Details are: " + issue); } boolean validIssueType = isValidIssueType(issueTypes, issueType); if (!validIssueType) { //set it to the first one?? throw new JiraException( "Unable to Create Issue: Issue type " + issueType + " is not allowed for Project " + projectKey + ". Valid Issue Types are: " + issueTypes + ". Issue Details are:" + issue); } //Validate the Assignee if (StringUtils.isBlank(assigneeName)) { //default it to the current Rest client user name assigneeName = super.getUsername(); } boolean assignable = isAssignable(projectKey, assigneeName); if (!assignable) { throw new JiraException("Unable to Create Issue: User " + assigneeName + " is not allowed to be assigned issues for Project " + projectKey + ". Issue Details are:" + issue); } //Validate required fields if (StringUtils.isBlank(summary)) { throw new JiraException("Unable to Create Issue: Summary is required"); } if (StringUtils.isBlank(description)) { throw new JiraException("Unable to Create Issue: Description is required"); } try { //Transform it to a CreateIssue CreateIssue createIssue = new CreateIssue(issue); //Post it GetIssue response = post("/issue/", createIssue, GetIssue.class); //transform the result back to a populated issue issue = getIssue(response.getKey()); log.info("Created JIRA Issue {}, - {}", issue.getKey(), issue.getSummary()); } catch (Exception e) { String message = "Error Creating Issue " + issue; throw new JiraException(message, e); } return issue; } /** * Create a new Jira Issue */ public Issue createIssue(String projectKey, String summary, String description, String issueType, String assigneeName) throws JiraException { //validate issuetype before creating Issue issue = new IssueBuilder(projectKey, issueType).setSummary(summary).setDescription(description).setAssignee(assigneeName).build(); return createIssue(issue); } public ServerInfo getServerInfo() throws JiraException { ServerInfo serverInfo = null; try { serverInfo = get("/serverInfo", null, ServerInfo.class); } catch (Exception e) { String message = "Error getting serverinfo " + e.getMessage(); throw new JiraException(message, e); } return serverInfo; } }