package flexagon.fd.model.integration.its.impl.redmine;
import flexagon.fd.model.integration.its.api.IssueTrackingSystem;
import flexagon.fd.model.integration.its.api.IssueTrackingSystemInstanceProject;
import flexagon.fd.model.integration.its.api.WorkItem;
import flexagon.fd.model.integration.its.api.WorkItemAttachment;
import flexagon.fd.model.integration.its.api.WorkItemComment;
import flexagon.fd.model.integration.its.api.WorkItemDetails;
import flexagon.fd.model.integration.its.api.WorkItemSearchInput;
import flexagon.fd.model.integration.its.api.WorkItemSearchResult;
import flexagon.fd.model.integration.its.api.WorkItemStatus;
import flexagon.fd.model.integration.its.api.WorkItemUpdate;
import flexagon.fd.model.integration.util.ApiException;
import flexagon.fd.model.integration.util.RestService;
import flexagon.ff.common.core.exceptions.FlexCheckedException;
import flexagon.ff.common.core.logging.FlexLogger;
import flexagon.ff.common.core.rest.FlexRESTClient;
import flexagon.ff.common.core.rest.FlexRESTClientResponse;
import flexagon.ff.common.core.utils.FlexCommonUtils;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.core.MediaType;
public class RedmineIssueTrackingSystem
extends IssueTrackingSystem
{
private static final String CLZ_NAM = RedmineIssueTrackingSystem.class.getName();
private static final FlexLogger LOG = FlexLogger.getLogger(CLZ_NAM);
private static final String FDREDMINE_URL = "FDREDMINE_URL";
private static final String FDREDMINE_TICKET_URL_PATTERN = "FDREDMINE_TICKET_URL_PATTERN";
private static final String FDREDMINE_ISSUE = "{REDMINE_ISSUE}";
private static final String FDREDMINE_USER_NAME = "FDREDMINE_USER_NAME";
private static final String FDREDMINE_PASSWORD = "FDREDMINE_PASSWORD";
public RedmineIssueTrackingSystem()
{
super();
}
private StringBuilder getURLBuilder()
{
StringBuilder urlBuilder = new StringBuilder((String) getProperties().get(FDREDMINE_URL));
return urlBuilder;
}
private String getPropertyForTicket(String propertyName, String pTicketNummber)
{
String origPropValue = (String) getProperties().get(propertyName);
if (origPropValue != null)
{
return origPropValue.replace(FDREDMINE_ISSUE, pTicketNummber);
}
else
{
return null;
}
}
private String getURLProperty(String propertyName, String pTicketNummber)
{
String methodName = "getURLProperty";
LOG.logFinestEntering(methodName);
StringBuilder ticketURLBuilder = getURLBuilder();
String urlProperty = ticketURLBuilder.append(getPropertyForTicket(propertyName, pTicketNummber)).toString();
LOG.logFinestExiting(methodName, urlProperty);
return urlProperty;
}
private String getHostNameURL()
{
return getURLBuilder().toString();
}
private String getUserName()
{
return (String) getProperties().get(FDREDMINE_USER_NAME);
}
private String getPassword()
{
return (String) getProperties().get(FDREDMINE_PASSWORD);
}
private FlexRESTClient getRestService()
throws ApiException
{
return RestService.getService(getHostNameURL(), getUserName(), getPassword());
}
private FlexRESTClient getTicketWebResource(String pTicketNummber)
throws ApiException
{
String ticketPath = getPropertyForTicket(FDREDMINE_TICKET_URL_PATTERN, pTicketNummber);
return getRestService().path(ticketPath);
}
public String getWorkItemStatus(WorkItem ticket)
throws ApiException
{
String methodName = "getTicketStatus";
LOG.logFinestEntering(methodName, ticket.getNumber());
FlexRESTClientResponse response;
try
{
response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).get());
}
catch (FlexCheckedException e)
{
throw new ApiException(e);
}
JsonObject jsonObject = RestService.readJsonObject(response.getResponseString());
JsonObject issueObject = (JsonObject) jsonObject.get("issue");
String statusName = null;
if (issueObject != null)
{
javax.json.JsonObject status = issueObject.getJsonObject("status");
if (status != null)
{
statusName = status.getString("name");
}
}
LOG.logFinestExiting(methodName, statusName);
return statusName;
}
/*
* Populates ticket's data (e.g. Description with summary)
*/
@Override
public void populateWorkItem(WorkItem ticket)
throws ApiException
{
String methodName = "populateWorkItem";
LOG.logFinestEntering(methodName, ticket);
FlexRESTClientResponse response;
try
{
response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).get());
}
catch (FlexCheckedException e)
{
throw new ApiException(e);
}
JsonObject jsonObject = RestService.readJsonObject(response.getResponseString());
if (jsonObject != null)
{
javax.json.JsonObject issue = jsonObject.getJsonObject("issue");
if (issue != null)
{
ticket.setDescription(issue.getString("description"));
javax.json.JsonObject tracker = issue.getJsonObject("tracker");
if (tracker != null)
{
ticket.setType(tracker.getString("name"));
}
}
}
LOG.logFinestExiting(methodName);
}
/*
* Builds absolute ticket's URL
*/
@Override
public String getWorkItemURL(WorkItem ticket)
throws ApiException
{
String methodName = "getWorkItemURL";
LOG.logFinestEntering(methodName, ticket);
String url = getURLProperty(FDREDMINE_TICKET_URL_PATTERN, ticket.getNumber());
url = url.replace(".json", "");
LOG.logFinestExiting(methodName, url);
return url;
}
/*
* Adds a comment to a ticket
*
*/
@Override
public void addCommentToWorkItem(WorkItem ticket, String comment)
throws ApiException
{
String methodName = "addCommentToWorkItem";
LOG.logFinestEntering(methodName, ticket.getNumber(), comment);
JsonObject commentJson = Json.createObjectBuilder().add("issue", Json.createObjectBuilder().add("notes", comment)).build();
FlexRESTClientResponse response;
try
{
response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(commentJson.toString()));
}
catch (FlexCheckedException e)
{
throw new ApiException(e);
}
LOG.logFinestExiting(methodName);
}
@SuppressWarnings("oracle.jdeveloper.java.insufficient-catch-block")
private int getStatusIdFromName(String pStatus)
throws ApiException
{
String methodName = "getStatusIdFromName";
LOG.logFinestEntering(methodName, pStatus);
int status_id = -1;
try
{
String statusNumber = pStatus.split("##")[0];
// If user saves 10 or 10##In Progress, let's try to parse it as number
status_id = Integer.parseInt(statusNumber);
}
catch (Exception nfe)
{
// not integer value, call API
}
if (status_id == -1)
{
// call API to do conversion
try
{
FlexRESTClientResponse response = RestService.checkResponse(getRestService().path("/issue_statuses.json").get());
Map<String, Integer> statusMap = parseIssueStatuses(response.getResponseString());
if (statusMap.containsKey(pStatus))
{
status_id = statusMap.get(pStatus);
}
}
catch (ApiException apie)
{
throw apie;
}
catch (FlexCheckedException e)
{
throw new ApiException(e);
}
}
LOG.logFinestExiting(methodName, status_id);
return status_id;
}
private Map<String, Integer> parseIssueStatuses(String pJson)
{
String methodName = "parseIssueStatuses";
LOG.logFinestEntering(methodName, pJson);
// Example output
// {
// "issue_statuses": [
// {
// "id": 1,
// "name": "New",
// "is_closed": false
// },
// {
// "id": 2,
// "name": "In Progress",
// "is_closed": false
// },
// {
// "id": 3,
// "name": "Resolved",
// "is_closed": false
// },
// {
// "id": 4,
// "name": "Closed",
// "is_closed": true
// },
// {
// "id": 5,
// "name": "Rejected",
// "is_closed": true
// },
// {
// "id": 6,
// "name": "Ready",
// "is_closed": false
// }
// ]
// }
Map<String, Integer> map = new HashMap<>();
JsonObject jsonObject = RestService.readJsonObject(pJson);
JsonArray ar = jsonObject.getJsonArray("issue_statuses");
for (int i = 0; i < ar.size(); i++)
{
String name = ar.getJsonObject(i).getString("name");
int id = ar.getJsonObject(i).getInt("id");
map.put(name, id);
}
LOG.logFinestExiting(methodName, map);
return map;
}
/*
* Changes ticket's status
*
*/
@Override
public void changeWorkItemStatusTo(WorkItem ticket, Serializable status)
throws ApiException
{
String methodName = "changeWorkItemStatusTo";
LOG.logFinestEntering(methodName, ticket.getNumber(), status);
// Redmine API needs id and not status name
JsonObjectBuilder addStatus = Json.createObjectBuilder().add("notes", "Status updated to " + status);
addStatus = addStatus.add("status_id", getStatusIdFromName((String) status));
JsonObject statusJson = Json.createObjectBuilder().add("issue", addStatus).build();
FlexRESTClientResponse response;
try
{
response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(statusJson.toString()));
}
catch (FlexCheckedException e)
{
throw new ApiException(e);
}
LOG.logFinestExiting(methodName);
}
@Override
public void checkConnection()
throws ApiException
{
String methodName = "checkConnection";
LOG.logFinestEntering(methodName);
try
{
String aboutUrl = "/users/current.json";
LOG.logInfo(methodName, "Checking Redmine connection for user {0} with path {1}", getUserName(), getHostNameURL() + aboutUrl);
FlexRESTClientResponse clientResponse = getRestService().path(aboutUrl).get();
LOG.logInfo(methodName, "Successfully invoked test connection URL {0}", clientResponse);
int statusCode = clientResponse.getResponse().getStatusInfo().getStatusCode();
if (statusCode == 401)
{
throw new ApiException("Invalid credentials.", "");
}
RestService.checkResponse(clientResponse);
LOG.logInfo(methodName, "Test connection response code looks valid, check content of response");
RestService.readJsonObject(clientResponse.getResponseString());
LOG.logInfo(methodName, "Validated that JSON data was received from test connction URL invocation");
}
catch (ApiException e)
{
LOG.logInfo(methodName, "ApiException in Test connection", e);
throw e;
}
catch (Exception e)
{
LOG.logInfo(methodName, "Failed in Test connection", e);
throw new ApiException("Connection failed. " + e.getMessage(), "");
}
LOG.logFinestExiting(methodName);
}
@Override
public Collection<String> parseWorkItemNumberFromChangeLogs(List<String> pChangeLogMessages, List<String> pTicketPatternList)
throws ApiException
{
String methodName = "parseWorkItemNumberFromChangeLogs";
LOG.logFinestEntering(methodName, pChangeLogMessages, pTicketPatternList);
Collection<String> ticketNumberList = new ArrayList<String>();
Collection<String> parsedTicketNumbers = flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList);
if (FlexCommonUtils.isNotEmpty(parsedTicketNumbers) && FlexCommonUtils.isNotEmpty(pTicketPatternList))
{
for (String parsedTicketWithPattern: parsedTicketNumbers)
{
for (String pattern: pTicketPatternList)
{
if (parsedTicketWithPattern.startsWith(pattern))
{
String ticketNumber = parsedTicketWithPattern.substring(pattern.length(), parsedTicketWithPattern.length());
ticketNumberList.add(ticketNumber);
}
}
}
}
LOG.logFinestExiting(methodName);
return ticketNumberList;
}
@Override
public Collection<WorkItemComment> getWorkItemComments(WorkItem pTicket, int i, int i2)
throws ApiException
{
// TODO Implement this method
return Collections.emptySet();
}
@Override
public Collection<WorkItemUpdate> getWorkItemHistory(WorkItem pTicket, int i, int i2)
throws ApiException
{
// TODO Implement this method
return Collections.emptySet();
}
@Override
public Collection<WorkItemAttachment> getWorkItemAttachments(WorkItem pTicket)
throws ApiException
{
// TODO Implement this method
return Collections.emptySet();
}
@Override
public WorkItemDetails getWorkItem(WorkItem pTicket)
throws ApiException
{
// TODO Implement this method
return null;
}
@Override
public InputStream getWorkItemAttachmentContent(WorkItem pTicket, Serializable pSerializable)
throws ApiException
{
// TODO Implement this method
return null;
}
@Override
public List<WorkItemDetails> getWorkItems(List<WorkItem> pList)
throws ApiException
{
// TODO Implement this method
return Collections.emptyList();
}
@Override
public List<WorkItemStatus> getAvailableWorkItemStatuses(WorkItem pWorkItem)
throws ApiException
{
// TODO Implement this method
return Collections.emptyList();
}
@Override
public List<WorkItemSearchResult> searchWorkItems(WorkItemSearchInput pInput)
throws ApiException
{
// TODO Implement this method (optional)
return Collections.emptyList();
}
@Override
public List<IssueTrackingSystemInstanceProject> getProjects()
throws ApiException
{
// TODO Implement this method (optional)
return Collections.emptyList();
}
@Override
public boolean isExternalSearchSupported()
throws ApiException
{
// TODO Implement this method (optional)
return false;
}
@Override
public boolean isExternalSearchExcludeKeysSupported()
throws ApiException
{
// TODO Implement this method (optional)
return false;
}
@Override
public boolean isExternalSearchProjectRequired()
throws ApiException
{
// TODO Implement this method (optional)
return false;
}
@Override
public boolean isExternalSearchProjectSupported()
throws ApiException
{
// TODO Implement this method (optional)
return false;
}
@Override
public boolean isExternalSearchFilterRequired()
throws ApiException
{
// TODO Implement this method (optional)
return false;
}
}