com.publicobject.issuesbrowser.IssuezillaXMLParser Maven / Gradle / Ivy
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package com.publicobject.issuesbrowser;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.matchers.Matchers;
import com.publicobject.misc.xml.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* Parses IssueZilla issues as described by their XML.
*
* Parsing supports DTD revision 1.2 only and may not work with prior or
* later versions of the IssueZilla XML format.
*
* @author Jesse Wilson
* @author James Lemieux
* @see Issuezilla DTD
*/
public class IssuezillaXMLParser {
/** the date format for "issue_when" is documented in the DTD to be 'yyyy-MM-dd HH:mm' but is actually 'yyyy-MM-dd HH:mm:ss' */
/** the date format for "delta_ts" is documented in the DTD to be 'yyyy-MM-dd HH:mm' but is actually 'yyyyMMddHHmmss' */
private static final DateFormat[] dateFormats = {new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("yyyyMMddHHmmss")};
// hardcode the servers in California
static {
final TimeZone laTimeZone = TimeZone.getTimeZone("America/Los_Angeles");
for (int i = 0; i < dateFormats.length; i++)
dateFormats[i].setTimeZone(laTimeZone);
}
private static Parser createParser(Project project) {
final Parser issueParser = new Parser();
// configure the Parser for Issues
// Parsing instructions for Issue
final XMLTagPath issueTag = new XMLTagPath("issuezilla").child("issue");
issueParser.addProcessor(issueTag.start(), Processors.createNewObject(Issue.class, new Class[] {Project.class}, new Object[] {project}));
issueParser.addProcessor(issueTag.attribute("status_code"), Processors.setterMethod(Issue.class, "statusCode", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("issue_id"), Processors.setterMethod(Issue.class, "id", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("issue_status"), Processors.setterMethod(Issue.class, "status", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("priority"), Processors.setterMethod(Issue.class, "priority", new PriorityConverter()));
issueParser.addProcessor(issueTag.child("resolution"), Processors.setterMethod(Issue.class, "resolution", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("component"), Processors.setterMethod(Issue.class, "component", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("version"), Processors.setterMethod(Issue.class, "version", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("rep_platform"), Processors.setterMethod(Issue.class, "repPlatform", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("assigned_to"), Processors.setterMethod(Issue.class, "assignedTo", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("delta_ts"), Processors.setterMethod(Issue.class, "deltaTimestamp", Converters.date(dateFormats)));
issueParser.addProcessor(issueTag.child("subcomponent"), Processors.setterMethod(Issue.class, "subcomponent", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("reporter"), Processors.setterMethod(Issue.class, "reporter", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("target_milestone"), Processors.setterMethod(Issue.class, "targetMilestone", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("issue_type"), Processors.setterMethod(Issue.class, "issueType", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("creation_ts"), Processors.setterMethod(Issue.class, "creationTimestamp", Converters.date(dateFormats)));
issueParser.addProcessor(issueTag.child("qa_contact"), Processors.setterMethod(Issue.class, "qAContact", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("status_whiteboard"), Processors.setterMethod(Issue.class, "statusWhiteboard", Converters.trim()));
issueParser.addProcessor(issueTag.child("issue_file_loc"), Processors.setterMethod(Issue.class, "fileLocation", Converters.trim()));
issueParser.addProcessor(issueTag.child("votes"), Processors.setterMethod(Issue.class, "votes", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("op_sys"), Processors.setterMethod(Issue.class, "operatingSystem", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("short_desc"), Processors.setterMethod(Issue.class, "shortDescription", Converters.trimAndIntern()));
issueParser.addProcessor(issueTag.child("keywords"), Processors.addToCollection(Issue.class, "keywords", Converters.trimAndIntern(), Matchers.nonNullAndNonEmptyString()));
issueParser.addProcessor(issueTag.child("cc"), Processors.addToCollection(Issue.class, "cC", Converters.trimAndIntern(), Matchers.nonNullAndNonEmptyString()));
issueParser.addProcessor(issueTag.end(), new AddIssueToTargetListProcessor());
// Parsing instructions for Description
final XMLTagPath descriptionTag = issueTag.child("long_desc");
issueParser.addProcessor(descriptionTag.start(), Processors.createNewObject(Description.class));
issueParser.addProcessor(descriptionTag.child("who"), Processors.setterMethod(Description.class, "who", Converters.trimAndIntern()));
issueParser.addProcessor(descriptionTag.child("issue_when"), Processors.setterMethod(Description.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(descriptionTag.child("thetext"), Processors.setterMethod(Description.class, "text", Converters.trim()));
issueParser.addProcessor(descriptionTag.end(), Processors.addToCollection(Issue.class, "descriptions"));
// Parsing instructions for Activity
final XMLTagPath activityTag = issueTag.child("activity");
issueParser.addProcessor(activityTag.start(), Processors.createNewObject(Activity.class));
issueParser.addProcessor(activityTag.child("user"), Processors.setterMethod(Activity.class, "user", Converters.trimAndIntern()));
issueParser.addProcessor(activityTag.child("when"), Processors.setterMethod(Activity.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(activityTag.child("field_name"), Processors.setterMethod(Activity.class, "field", Converters.trimAndIntern()));
issueParser.addProcessor(activityTag.child("field_desc"), Processors.setterMethod(Activity.class, "fieldDescription", Converters.trimAndIntern()));
issueParser.addProcessor(activityTag.child("oldvalue"), Processors.setterMethod(Activity.class, "oldValue", Converters.trimAndIntern()));
issueParser.addProcessor(activityTag.child("newvalue"), Processors.setterMethod(Activity.class, "newValue", Converters.trimAndIntern()));
issueParser.addProcessor(activityTag.end(), Processors.addToCollection(Issue.class, "activities"));
// Parsing instructions for Attachment
final XMLTagPath attachmentTag = issueTag.child("attachment");
issueParser.addProcessor(attachmentTag.start(), Processors.createNewObject(Attachment.class));
issueParser.addProcessor(attachmentTag.child("mimetype"), Processors.setterMethod(Attachment.class, "mimeType", Converters.trimAndIntern()));
issueParser.addProcessor(attachmentTag.child("attachid"), Processors.setterMethod(Attachment.class, "attachId", Converters.trimAndIntern()));
issueParser.addProcessor(attachmentTag.child("date"), Processors.setterMethod(Attachment.class, "date", Converters.date(dateFormats)));
issueParser.addProcessor(attachmentTag.child("desc"), Processors.setterMethod(Attachment.class, "description", Converters.trim()));
issueParser.addProcessor(attachmentTag.child("ispatch"), Processors.setterMethod(Attachment.class, "isPatch", Converters.trim()));
issueParser.addProcessor(attachmentTag.child("filename"), Processors.setterMethod(Attachment.class, "filename", Converters.trim()));
issueParser.addProcessor(attachmentTag.child("submitter_id"), Processors.setterMethod(Attachment.class, "submitterId", Converters.trimAndIntern()));
issueParser.addProcessor(attachmentTag.child("submitting_username"), Processors.setterMethod(Attachment.class, "submitterUsername", Converters.trimAndIntern()));
issueParser.addProcessor(attachmentTag.child("data"), Processors.setterMethod(Attachment.class, "data", Converters.trim()));
issueParser.addProcessor(attachmentTag.child("attachment_iz_url"), Processors.setterMethod(Attachment.class, "attachmentIzUrl", Converters.trim()));
issueParser.addProcessor(attachmentTag.end(), Processors.addToCollection(Issue.class, "attachments"));
// Parsing instructions for duplicante PeerIssues
final XMLTagPath hasDuplicatesTag = issueTag.child("has_duplicates");
issueParser.addProcessor(hasDuplicatesTag.start(), Processors.createNewObject(PeerIssue.class));
issueParser.addProcessor(hasDuplicatesTag.child("issue_id"), Processors.setterMethod(PeerIssue.class, "issueId", Converters.trimAndIntern()));
issueParser.addProcessor(hasDuplicatesTag.child("who"), Processors.setterMethod(PeerIssue.class, "who", Converters.trimAndIntern()));
issueParser.addProcessor(hasDuplicatesTag.child("when"), Processors.setterMethod(PeerIssue.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(hasDuplicatesTag.end(), Processors.addToCollection(Issue.class, "duplicates"));
// Parsing instructions for a duplicate PeerIssue
final XMLTagPath isDuplicateTag = issueTag.child("is_duplicate");
issueParser.addProcessor(isDuplicateTag.start(), Processors.createNewObject(PeerIssue.class));
issueParser.addProcessor(isDuplicateTag.child("issue_id"), Processors.setterMethod(PeerIssue.class, "issueId", Converters.trimAndIntern()));
issueParser.addProcessor(isDuplicateTag.child("who"), Processors.setterMethod(PeerIssue.class, "who", Converters.trimAndIntern()));
issueParser.addProcessor(isDuplicateTag.child("when"), Processors.setterMethod(PeerIssue.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(isDuplicateTag.end(), Processors.setterMethod(Issue.class, "duplicate"));
// Parsing instructions for a dependent PeerIssue
final XMLTagPath dependsOnTag = issueTag.child("dependson");
issueParser.addProcessor(dependsOnTag.start(), Processors.createNewObject(PeerIssue.class));
issueParser.addProcessor(dependsOnTag.child("issue_id"), Processors.setterMethod(PeerIssue.class, "issueId", Converters.trimAndIntern()));
issueParser.addProcessor(dependsOnTag.child("who"), Processors.setterMethod(PeerIssue.class, "who", Converters.trimAndIntern()));
issueParser.addProcessor(dependsOnTag.child("when"), Processors.setterMethod(PeerIssue.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(dependsOnTag.end(), Processors.addToCollection(Issue.class, "dependsOn"));
// Parsing instructions for a blocking PeerIssue
final XMLTagPath blocksTag = issueTag.child("blocks");
issueParser.addProcessor(blocksTag.start(), Processors.createNewObject(PeerIssue.class));
issueParser.addProcessor(blocksTag.child("issue_id"), Processors.setterMethod(PeerIssue.class, "issueId", Converters.trimAndIntern()));
issueParser.addProcessor(blocksTag.child("who"), Processors.setterMethod(PeerIssue.class, "who", Converters.trimAndIntern()));
issueParser.addProcessor(blocksTag.child("when"), Processors.setterMethod(PeerIssue.class, "when", Converters.date(dateFormats)));
issueParser.addProcessor(blocksTag.end(), Processors.addToCollection(Issue.class, "blocks"));
return issueParser;
}
/**
* Loads issues from the specified URL.
*/
public static void loadIssues(EventList target, Project owner) throws IOException {
int issuesPerRequest = 100;
String baseQueryUrl = owner.getIssueQueryUri();
// continuously load issues until there's no more
while (true) {
// figure out how many to load
int currentTotal = target.size();
int nextTotal = currentTotal + issuesPerRequest;
// assemble the issue ID argument
StringBuffer idArg = new StringBuffer();
for(int i = currentTotal + 1; i <= nextTotal; i++) {
idArg.append(i);
if(i < nextTotal) idArg.append(":");
}
// prepare a stream
final String urlAsString = baseQueryUrl + "?include_attachments=false&id=" + idArg;
URL issuesUrl = new URL(urlAsString);
InputStream issuesInStream = issuesUrl.openStream();
// parse
loadIssues(target, issuesInStream, owner);
// if we couldn't load everything, we've consumed everything
if (target.size() < nextTotal) return;
}
}
/**
* Parses the Issuezilla XML document on the specified input stream into a List
* of issues. While the parsing is taking place this writes some simple Java
* commands to reproduce a lightweight version of this list. This is useful
* to load the issues as code rather than XML.
*/
public static void loadIssues(EventList target, InputStream source, Project owner) throws IOException {
createParser(owner).parse(source, target);
}
/**
* This Converter can lookup Priority objects using Strings.
*/
private static class PriorityConverter implements Converter {
@Override
public Priority convert(String value) {
return Priority.lookupIssuzilla(value.trim());
}
}
/**
* This Processor adds a completely built Issue to the target EventList.
* It also performs some late processing of the Issue, namely computing the
* state changes of the Issue. This Processor only adds the Issue to the
* target EventList if the status code is 200, indicating that the Issue
* was loaded successfully.
*/
private static class AddIssueToTargetListProcessor implements PopProcessor,Issue> {
private final Date loadingStarted = new Date();
@Override
public void process(EventList issues, Issue issue) {
final String statusCode = issue.getStatusCode();
// add the issue to the list if it was found okay
if ("200".equals(statusCode)) {
// compute the timeline of state changes now that we have loaded the entire Issue
issue.getStateChanges().addAll(Issue.computeStateChanges(issue, loadingStarted));
// add the Issue to the list of Issues
issues.add(issue);
}
}
}
}