de.decoit.rt.rest.RtRestTicketResponseParser.java Source code

Java tutorial

Introduction

Here is the source code for de.decoit.rt.rest.RtRestTicketResponseParser.java

Source

/* 
 * Copyright (C) 2015 DECOIT GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.decoit.rt.rest;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import de.decoit.rt.RtException;
import de.decoit.rt.model.RtTicket;
import de.decoit.rt.model.RtTicket.RtTicketStatus;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

/**
 * This class provides methods to parse responses from ticket requests to the RT REST API.
 *
 * @author Thomas Rix (rix@decoit.de)
 */
class RtRestTicketResponseParser extends RtRestResponseParser {
    private static final Logger LOG = Logger.getLogger(RtRestTicketResponseParser.class);
    private static final SimpleDateFormat sdf;

    static {
        // Create a parser for the REST date format
        sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
    }

    /**
     * Protected default constructor, this is a static only class
     */
    protected RtRestTicketResponseParser() {
    }

    /**
     * Create a RtTicket instance from the response of a RT REST request. This only works for ticket properties requests, other responses may return defect objects.
     *
     * @param response Response text received from the RT REST API
     * @return A ticket object filled with the values from the response string
     *
     * @throws RtException If a line starting with "#" was found (means error for ticket properties requests)
     */
    static RtTicket parseTicket(String response) throws RtException {
        RtTicket ticket = new RtTicket();

        // Split lines in response string
        String[] responseParts = response.split("\n");

        for (String line : responseParts) {
            if (StringUtils.isBlank(line)) {
                // Blank line, ignore
            } else if (line.startsWith("RT/")) {
                // Status line, ignore
            } else if (line.startsWith("#")) {
                // Message line, in case of ticket query means something went wrong, raise exception
                throw new RtException(line.substring(2));
            } else if (line.startsWith("CF.{")) {
                // New style custom field line, parse and add to custom fields of the ticket
                String[] lineParts = line.split(":", 2);
                lineParts[1] = lineParts[1].trim();

                Pattern cfNamePattern = Pattern.compile("^CF\\.\\{(.+?)\\}$");
                Matcher cfNameMatcher = cfNamePattern.matcher(lineParts[0]);
                if (cfNameMatcher.matches()) {
                    String cfName = cfNameMatcher.group(1);

                    ticket.addCustomField(cfName, lineParts[1]);
                } else {
                    LOG.warn("Invalid custom field line detected: " + line);
                }
            } else if (line.startsWith("CF-")) {
                // Old style custom field line, parse and add to custom fields of the ticket
                String[] lineParts = line.split(":", 2);
                lineParts[1] = lineParts[1].trim();

                Pattern cfNamePattern = Pattern.compile("^CF-(.+?)$");
                Matcher cfNameMatcher = cfNamePattern.matcher(lineParts[0]);
                if (cfNameMatcher.matches()) {
                    String cfName = cfNameMatcher.group(1);

                    ticket.addCustomField(cfName, lineParts[1]);
                } else {
                    LOG.warn("Invalid custom field line detected: " + line);
                }
            } else {
                Pattern idPattern = Pattern.compile("^ticket/(\\d+)$");
                Matcher idMatcher;
                Pattern timePattern = Pattern.compile("^(\\d+?) (minutes)?$");
                Matcher timeMatcher;

                String[] lineParts = line.split(":", 2);
                lineParts[1] = lineParts[1].trim();

                switch (lineParts[0]) {
                case "id":
                    idMatcher = idPattern.matcher(lineParts[1]);

                    if (idMatcher.matches()) {
                        ticket.setId(Long.parseLong(idMatcher.group(1)));
                    } else {
                        throw new RtException("Invalid ticket ID pattern: " + lineParts[1]);
                    }
                    break;
                case "Queue":
                    ticket.setQueue(lineParts[1]);
                    break;
                case "Owner":
                    ticket.setOwner(lineParts[1]);
                    break;
                case "Creator":
                    ticket.setCreator(lineParts[1]);
                    break;
                case "Subject":
                    ticket.setSubject(lineParts[1]);
                    break;
                case "Status":
                    ticket.setStatus(RtTicketStatus.fromStatusText(lineParts[1]));
                    break;
                case "Priority":
                    ticket.setPriority(Integer.parseInt(lineParts[1]));
                    break;
                case "InitialPriority":
                    ticket.setInitialPriority(Integer.parseInt(lineParts[1]));
                    break;
                case "FinalPriority":
                    ticket.setFinalPriority(Integer.parseInt(lineParts[1]));
                    break;
                case "Requestors":
                    ticket.setRequestors(Lists.newArrayList(Splitter.on(",").trimResults().split(lineParts[1])));
                    break;
                case "Cc":
                    ticket.setCc(Lists.newArrayList(Splitter.on(",").trimResults().split(lineParts[1])));
                    break;
                case "AdminCc":
                    ticket.setAdminCc(Lists.newArrayList(Splitter.on(",").trimResults().split(lineParts[1])));
                    break;
                case "Created":
                    try {
                        ticket.setCreated(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setCreated(null);
                    }
                    break;
                case "Starts":
                    try {
                        ticket.setStarts(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setStarts(null);
                    }
                    break;
                case "Started":
                    try {
                        ticket.setStarted(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setStarted(null);
                    }
                    break;
                case "Due":
                    try {
                        ticket.setDue(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setDue(null);
                    }
                    break;
                case "Resolved":
                    try {
                        ticket.setResolved(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setResolved(null);
                    }
                    break;
                case "Told":
                    try {
                        ticket.setTold(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setTold(null);
                    }
                    break;
                case "LastUpdated":
                    try {
                        ticket.setLastUpdated(parseDateString(lineParts[1]));
                    } catch (ParseException ex) {
                        ticket.setLastUpdated(null);
                    }
                    break;
                case "TimeEstimated":
                    timeMatcher = timePattern.matcher(lineParts[1]);
                    if (timeMatcher.matches()) {
                        ticket.setTimeEstimated(Integer.parseInt(timeMatcher.group(1)));
                    } else {
                        ticket.setTimeEstimated(0);
                    }
                    break;
                case "TimeWorked":
                    timeMatcher = timePattern.matcher(lineParts[1]);
                    if (timeMatcher.matches()) {
                        ticket.setTimeWorked(Integer.parseInt(timeMatcher.group(1)));
                    } else {
                        ticket.setTimeWorked(0);
                    }
                    break;
                case "TimeLeft":
                    timeMatcher = timePattern.matcher(lineParts[1]);
                    if (timeMatcher.matches()) {
                        ticket.setTimeLeft(Integer.parseInt(timeMatcher.group(1)));
                    } else {
                        ticket.setTimeLeft(0);
                    }
                    break;
                default:
                    /* Ignore */
                }
            }
        }

        return ticket;
    }

    /**
     * Parse a list of tickets into a Java list.
     * The reponse list must be in the format of a long format ticket search. It uses the parseTicket() method to
     * create ticket objects from the list parts. Ordering is preserved and will be the same as in the response
     * string.
     *
     * @param response Response text received from the RT REST API
     * @return A list of tickets
     * @throws RtException
     */
    static List<RtTicket> parseTickets(String response) throws RtException {
        ArrayList<RtTicket> ticketList = new ArrayList<>();

        if (response.contains("No matching results.")) {
            // No results found, return empty list
            return ticketList;
        }

        String[] responseParts = response.split(RtRestResponseParser.LIST_DELIMITER);

        for (String part : responseParts) {
            ticketList.add(parseTicket(part));
        }

        return ticketList;
    }

    /**
     * Parse a response text to determine if a ticket was created successfully and return the new ticket's ID.
     * It checks the message line of the response (beginning with a #) for the success message.
     *
     * @param response Response text received from the RT REST API
     * @return The ID of the created ticket
     */
    static long parseTicketCreated(String response) throws RtException {
        // Split lines in response string
        String[] responseParts = response.split("\n");

        for (String line : responseParts) {
            // Line with status message, it is the only one that is of interest in this case
            if (line.startsWith("#")) {
                Pattern p = Pattern.compile("^# Ticket (\\d+?) created\\.$");
                Matcher m = p.matcher(line);

                if (m.matches()) {
                    return Long.parseLong(m.group(1));
                }
            } else {
                // All other lines, ignore
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Ticket creation failed");
            LOG.debug(response);
        }
        throw new RtException("Ticket creation failed");
    }

    /**
     * Parse the response of an update ticket request to decide if the update was successful.
     *
     * @param response Response text received from the RT REST API
     * @return true if the ticket update was succcessful
     * @throws RtException if the ticket update failed
     */
    static boolean parseTicketEdited(String response) throws RtException {
        // Split lines in response string
        String[] responseParts = response.split("\n");

        for (String line : responseParts) {
            // Line with status message, it is the only one that is of interest in this case
            if (line.startsWith("#")) {
                Pattern p = Pattern.compile("^# Ticket \\d+? updated\\.$");
                Matcher m = p.matcher(line);

                if (m.matches()) {
                    return true;
                }
            } else {
                // All other lines, ignore
            }
        }

        throw new RtException("Ticket edit failed");
    }

    /**
     * Build a content string from a ticket object.
     * This string contains all information set in the object and can be used to create a new ticket
     * or update an existing one.
     *
     * @param ticket Ticket object to transform into a string
     * @param newTicket Whether the string should be used for creation (true) or update (false)
     * @param oldTicket In case of ticket update, the existing ticket must be provided here. In case of ticket creation this may be null.
     * @return The generated content string
     */
    static String ticketToString(RtTicket ticket, boolean newTicket, RtTicket oldTicket) throws RtException {
        if (newTicket) {
            return newTicketToString(ticket);
        } else if (!newTicket && oldTicket != null) {
            return editTicketToString(ticket, oldTicket);
        } else {
            throw new RtException("No existing ticket provided for ticket update");
        }
    }

    /**
     * Parse the date string returned from RT into a native Java Date object
     *
     * @param date Date string returned from RT
     * @return A native Date object representing the provided date
     * @throws ParseException If the date string could not be parsed
     */
    static Date parseDateString(String date) throws ParseException {
        return sdf.parse(date);
    }

    /**
     * Format a Date object into the date string used by the specific implementation
     *
     * @param date Native Date object
     * @return Date string required for the specific implementation
     */
    static String formatDateString(Date date) {
        if (date != null) {
            return sdf.format(date);
        } else {
            return "";
        }
    }

    private static String newTicketToString(RtTicket ticket) {
        StringBuilder sb = new StringBuilder();
        Joiner commaJoiner = Joiner.on(",").skipNulls();

        // Ticket ID
        sb.append("id: ticket/new\n");

        // Ticket queue
        sb.append("Queue: ");
        sb.append(ticket.getQueue());
        sb.append("\n");

        // Owner
        sb.append("Owner: ");
        sb.append(ticket.getOwner());
        sb.append("\n");

        // Subject
        sb.append("Subject: ");
        sb.append(ticket.getSubject());
        sb.append("\n");

        // Priority
        sb.append("Priority: ");
        sb.append(ticket.getPriority());
        sb.append("\n");

        // Initial Priority
        sb.append("InitialPriority: ");
        sb.append(ticket.getInitialPriority());
        sb.append("\n");

        // Final Priority
        sb.append("FinalPriority: ");
        sb.append(ticket.getFinalPriority());
        sb.append("\n");

        // Requestors
        sb.append("Requestors: ");
        sb.append(commaJoiner.join(ticket.getRequestors()));
        sb.append("\n");

        // CC
        sb.append("Cc: ");
        sb.append(commaJoiner.join(ticket.getCc()));
        sb.append("\n");

        // AdminCC
        sb.append("AdminCc: ");
        sb.append(commaJoiner.join(ticket.getAdminCc()));
        sb.append("\n");

        // Starts
        sb.append("Starts: ");
        sb.append(formatDateString(ticket.getStarts()));
        sb.append("\n");

        // Due
        sb.append("Due: ");
        sb.append(formatDateString(ticket.getDue()));
        sb.append("\n");

        // Time estimated
        sb.append("TimeEstimated: ");
        sb.append(ticket.getTimeEstimated());
        sb.append("\n");

        // Time worked
        sb.append("TimeWorked: ");
        sb.append(ticket.getTimeWorked());
        sb.append("\n");

        // Time left
        sb.append("TimeLeft: ");
        sb.append(ticket.getTimeLeft());
        sb.append("\n");

        // Text
        if (ticket.getText() != null) {
            sb.append("Text: ");
            sb.append(ticket.getText().replaceAll("\n", "\n "));
            sb.append("\n");
        }

        if (ticket.getCustomFields() != null) {
            for (Entry<String, String> e : ticket.getCustomFields().entrySet()) {
                sb.append("CF.{");
                sb.append(e.getKey());
                sb.append("}: ");
                sb.append(e.getValue());
                sb.append("\n");
            }
        }

        return sb.toString();
    }

    private static String editTicketToString(RtTicket ticket, RtTicket oldTicket) {
        StringBuilder sb = new StringBuilder();
        Joiner commaJoiner = Joiner.on(",").skipNulls();

        // Ticket queue
        if (!oldTicket.getQueue().equals(ticket.getQueue())) {
            sb.append("Queue: ");
            sb.append(ticket.getQueue());
            sb.append("\n");
        }

        // Owner
        if (!oldTicket.getOwner().equals(ticket.getOwner())) {
            sb.append("Owner: ");
            sb.append(ticket.getOwner());
            sb.append("\n");
        }

        // Subject
        if (!oldTicket.getSubject().equals(ticket.getSubject())) {
            sb.append("Subject: ");
            sb.append(ticket.getSubject());
            sb.append("\n");
        }

        // Status
        if (oldTicket.getStatus() != ticket.getStatus()) {
            sb.append("Status: ");
            sb.append(ticket.getStatus());
            sb.append("\n");
        }

        // Priority
        if (oldTicket.getPriority() != ticket.getPriority()) {
            sb.append("Priority: ");
            sb.append(ticket.getPriority());
            sb.append("\n");
        }

        // Initial Priority
        if (oldTicket.getInitialPriority() != ticket.getInitialPriority()) {
            sb.append("InitialPriority: ");
            sb.append(ticket.getInitialPriority());
            sb.append("\n");
        }

        // Final Priority
        if (oldTicket.getFinalPriority() != ticket.getFinalPriority()) {
            sb.append("FinalPriority: ");
            sb.append(ticket.getFinalPriority());
            sb.append("\n");
        }

        // Requestors
        List<String> oldRequestorsList = oldTicket.getRequestors();
        List<String> newRequestorsList = ticket.getRequestors();
        boolean requestorsEqual = false;
        if (oldRequestorsList.size() == newRequestorsList.size()) {
            Collections.sort(oldRequestorsList);
            Collections.sort(newRequestorsList);

            if (oldRequestorsList.equals(newRequestorsList)) {
                requestorsEqual = true;
            }
        }
        if (!requestorsEqual) {
            sb.append("Requestors: ");
            sb.append(commaJoiner.join(ticket.getRequestors()));
            sb.append("\n");
        }

        // CC
        List<String> oldCcList = oldTicket.getCc();
        List<String> newCcList = ticket.getCc();
        boolean ccEqual = false;
        if (oldCcList.size() == newCcList.size()) {
            Collections.sort(oldCcList);
            Collections.sort(newCcList);

            if (oldCcList.equals(newCcList)) {
                ccEqual = true;
            }
        }
        if (!ccEqual) {
            sb.append("Cc: ");
            sb.append(commaJoiner.join(ticket.getCc()));
            sb.append("\n");
        }

        // AdminCC
        List<String> oldAdminCcList = oldTicket.getAdminCc();
        List<String> newAdminCcList = ticket.getAdminCc();
        boolean adminCcEqual = false;
        if (oldAdminCcList.size() == newAdminCcList.size()) {
            Collections.sort(oldAdminCcList);
            Collections.sort(newAdminCcList);

            if (oldAdminCcList.equals(newAdminCcList)) {
                adminCcEqual = true;
            }
        }
        if (!adminCcEqual) {
            sb.append("AdminCc: ");
            sb.append(commaJoiner.join(ticket.getAdminCc()));
            sb.append("\n");
        }

        // Starts
        if (ticket.getStarts() != null && !ticket.getStarts().equals(oldTicket.getStarts())) {
            sb.append("Starts: ");
            sb.append(formatDateString(ticket.getStarts()));
            sb.append("\n");
        }

        // Started
        if (ticket.getStarted() != null && !ticket.getStarted().equals(oldTicket.getStarted())) {
            sb.append("Started: ");
            sb.append(formatDateString(ticket.getStarted()));
            sb.append("\n");
        }

        // Due
        if (ticket.getDue() != null && !ticket.getDue().equals(oldTicket.getDue())) {
            sb.append("Due: ");
            sb.append(formatDateString(ticket.getDue()));
            sb.append("\n");
        }

        // Resolved
        if (ticket.getResolved() != null && !ticket.getResolved().equals(oldTicket.getResolved())) {
            sb.append("Resolved: ");
            sb.append(formatDateString(ticket.getResolved()));
            sb.append("\n");
        }

        // Told
        if (ticket.getTold() != null && !ticket.getTold().equals(oldTicket.getTold())) {
            sb.append("Told: ");
            sb.append(formatDateString(ticket.getTold()));
            sb.append("\n");
        }

        // Time estimated
        if (oldTicket.getTimeEstimated() != ticket.getTimeEstimated()) {
            sb.append("TimeEstimated: ");
            sb.append(ticket.getTimeEstimated());
            sb.append("\n");
        }

        // Time worked
        if (oldTicket.getTimeWorked() != ticket.getTimeWorked()) {
            sb.append("TimeWorked: ");
            sb.append(ticket.getTimeWorked());
            sb.append("\n");
        }

        // Time left
        if (oldTicket.getTimeLeft() != ticket.getTimeLeft()) {
            sb.append("TimeLeft: ");
            sb.append(ticket.getTimeLeft());
            sb.append("\n");
        }

        // Text
        if (ticket.getText() != null) {
            sb.append("Text: ");
            sb.append(ticket.getText().replaceAll("\n", "\n "));
            sb.append("\n");
        }

        if (ticket.getCustomFields() != null) {
            for (Entry<String, String> e : ticket.getCustomFields().entrySet()) {
                sb.append("CF.{");
                sb.append(e.getKey());
                sb.append("}: ");
                sb.append(e.getValue());
                sb.append("\n");
            }
        }

        return sb.toString();
    }
}