| /******************************************************************************* |
| * Copyright (c) 2004, 2008 Tasktop Technologies and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.bugzilla.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.eclipse.mylyn.tasks.core.IRepositoryPerson; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttribute; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; |
| import org.eclipse.mylyn.tasks.core.data.TaskCommentMapper; |
| import org.eclipse.mylyn.tasks.core.data.TaskData; |
| import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * Parser for xml bugzilla reports. |
| * |
| * @author Rob Elves |
| * @author Hiroyuki Inaba (internationalization) |
| */ |
| public class SaxMultiBugReportContentHandler extends DefaultHandler { |
| |
| private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ |
| |
| private static final String ID_STRING_BEGIN = " (id="; //$NON-NLS-1$ |
| |
| private static final String ID_STRING_END = ")"; //$NON-NLS-1$ |
| |
| private StringBuffer characters; |
| |
| private TaskComment taskComment; |
| |
| private Map<String, TaskCommentMapper> attachIdToComment = new HashMap<String, TaskCommentMapper>(); |
| |
| private int commentNum = 0; |
| |
| private TaskAttachmentMapper attachment; |
| |
| private final Map<String, TaskData> taskDataMap; |
| |
| private TaskData repositoryTaskData; |
| |
| private List<TaskComment> longDescs; |
| |
| private String errorMessage = null; |
| |
| private final List<BugzillaCustomField> customFields; |
| |
| private final TaskDataCollector collector; |
| |
| private boolean isDeprecated = false; |
| |
| private boolean isPatch = false; |
| |
| private TaskAttribute attachmentAttribute; |
| |
| public SaxMultiBugReportContentHandler(TaskAttributeMapper mapper, TaskDataCollector collector, |
| Map<String, TaskData> taskDataMap, List<BugzillaCustomField> customFields) { |
| this.taskDataMap = taskDataMap; |
| this.customFields = customFields; |
| this.collector = collector; |
| } |
| |
| public boolean errorOccurred() { |
| return errorMessage != null; |
| } |
| |
| public String getErrorMessage() { |
| return errorMessage; |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) throws SAXException { |
| characters.append(ch, start, length); |
| //System.err.println(String.copyValueOf(ch, start, length)); |
| } |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
| characters = new StringBuffer(); |
| BugzillaAttribute tag = BugzillaAttribute.UNKNOWN; |
| if (localName.startsWith(BugzillaCustomField.CUSTOM_FIELD_PREFIX)) { |
| return; |
| } |
| try { |
| tag = BugzillaAttribute.valueOf(localName.trim().toUpperCase(Locale.ENGLISH)); |
| } catch (RuntimeException e) { |
| if (e instanceof IllegalArgumentException) { |
| // ignore unrecognized tags |
| return; |
| } |
| throw e; |
| } |
| switch (tag) { |
| case BUGZILLA: |
| // Note: here we can get the bugzilla version if necessary |
| break; |
| case BUG: |
| if (attributes != null && (attributes.getValue("error") != null)) { //$NON-NLS-1$ |
| errorMessage = attributes.getValue("error"); //$NON-NLS-1$ |
| } |
| attachIdToComment = new HashMap<String, TaskCommentMapper>(); |
| commentNum = 0; |
| taskComment = null; |
| longDescs = new ArrayList<TaskComment>(); |
| break; |
| case LONG_DESC: |
| taskComment = new TaskComment(commentNum++); |
| break; |
| case WHO: |
| if (taskComment != null) { |
| if (attributes != null && attributes.getLength() > 0) { |
| String name = attributes.getValue(ATTRIBUTE_NAME); |
| if (name != null) { |
| taskComment.authorName = name; |
| } |
| } |
| } |
| break; |
| case REPORTER: |
| if (attributes != null && attributes.getLength() > 0) { |
| String name = attributes.getValue(ATTRIBUTE_NAME); |
| if (name != null) { |
| BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.REPORTER_NAME) |
| .setValue(name); |
| } |
| } |
| break; |
| case QA_CONTACT: |
| if (attributes != null && attributes.getLength() > 0) { |
| String name = attributes.getValue(ATTRIBUTE_NAME); |
| if (name != null) { |
| BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.QA_CONTACT_NAME) |
| .setValue(name); |
| } |
| } |
| break; |
| case ASSIGNED_TO: |
| if (attributes != null && attributes.getLength() > 0) { |
| String name = attributes.getValue(ATTRIBUTE_NAME); |
| if (name != null) { |
| BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.ASSIGNED_TO_NAME) |
| .setValue(name); |
| } |
| } |
| break; |
| case ATTACHMENT: |
| if (attributes != null) { |
| isDeprecated = "1".equals(attributes.getValue(BugzillaAttribute.IS_OBSOLETE.getKey())); //$NON-NLS-1$ |
| isPatch = "1".equals(attributes.getValue(BugzillaAttribute.IS_PATCH.getKey())); //$NON-NLS-1$ |
| } |
| break; |
| case FLAG: |
| if (attributes != null && attributes.getLength() > 0) { |
| String name = attributes.getValue(ATTRIBUTE_NAME); |
| if (name != null) { |
| BugzillaFlagMapper mapper = new BugzillaFlagMapper(); |
| String requestee = attributes.getValue("requestee"); //$NON-NLS-1$ |
| mapper.setRequestee(requestee); |
| String setter = attributes.getValue("setter"); //$NON-NLS-1$ |
| mapper.setSetter(setter); |
| String status = attributes.getValue("status"); //$NON-NLS-1$ |
| mapper.setState(status); |
| mapper.setFlagId(name); |
| String id = attributes.getValue("id"); //$NON-NLS-1$ |
| if (id != null && !id.equals("")) { //$NON-NLS-1$ |
| /* |
| * for version 3.2rc1 and 3.2.rc2 the id was not defined so we ignore |
| * the definition |
| */ |
| try { |
| mapper.setNumber(Integer.valueOf(id)); |
| TaskAttribute attribute; |
| if (attachmentAttribute != null) { |
| attribute = attachmentAttribute.createAttribute("task.common.kind.flag" + id); //$NON-NLS-1$ |
| } else { |
| attribute = repositoryTaskData.getRoot().createAttribute("task.common.kind.flag" + id); //$NON-NLS-1$ |
| } |
| mapper.applyTo(attribute); |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| break; |
| } |
| |
| } |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) throws SAXException { |
| |
| //remove whitespaces from the end of the parsed Text |
| while (characters.length() > 0 && Character.isWhitespace(characters.charAt(characters.length() - 1))) { |
| characters.setLength(characters.length() - 1); |
| } |
| |
| String parsedText = characters.toString(); |
| |
| if (localName.startsWith(BugzillaCustomField.CUSTOM_FIELD_PREFIX)) { |
| TaskAttribute endAttribute = repositoryTaskData.getRoot().getAttribute(localName); |
| if (endAttribute == null) { |
| String desc = "???"; //$NON-NLS-1$ |
| BugzillaCustomField customField = null; |
| for (BugzillaCustomField bugzillaCustomField : customFields) { |
| if (localName.equals(bugzillaCustomField.getName())) { |
| customField = bugzillaCustomField; |
| break; |
| } |
| } |
| if (customField != null) { |
| TaskAttribute atr = repositoryTaskData.getRoot().createAttribute(localName); |
| desc = customField.getDescription(); |
| atr.getMetaData().defaults().setLabel(desc).setReadOnly(false); |
| atr.getMetaData().setKind(TaskAttribute.KIND_DEFAULT); |
| atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); |
| switch (customField.getType()) { |
| case 1: // Free Text |
| atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); |
| break; |
| case 2: // Drop Down |
| atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); |
| break; |
| case 3: // Multiple-Selection Box |
| atr.getMetaData().setType(TaskAttribute.TYPE_MULTI_SELECT); |
| break; |
| case 4: // Large Text Box |
| atr.getMetaData().setType(TaskAttribute.TYPE_LONG_TEXT); |
| break; |
| case 5: // Date/Time |
| atr.getMetaData().setType(TaskAttribute.TYPE_DATETIME); |
| break; |
| |
| default: |
| List<String> options = customField.getOptions(); |
| if (options.size() > 0) { |
| atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); |
| } else { |
| atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); |
| } |
| } |
| atr.getMetaData().setReadOnly(false); |
| atr.setValue(parsedText); |
| } |
| } else { |
| endAttribute.addValue(parsedText); |
| } |
| } |
| |
| BugzillaAttribute tag = BugzillaAttribute.UNKNOWN; |
| try { |
| tag = BugzillaAttribute.valueOf(localName.trim().toUpperCase(Locale.ENGLISH)); |
| } catch (RuntimeException e) { |
| if (e instanceof IllegalArgumentException) { |
| // ignore unrecognized tags |
| return; |
| } |
| throw e; |
| } |
| switch (tag) { |
| case BUG_ID: { |
| try { |
| repositoryTaskData = taskDataMap.get(parsedText.trim()); |
| if (repositoryTaskData == null) { |
| errorMessage = parsedText + Messages.SaxMultiBugReportContentHandler_id_not_found; |
| } |
| } catch (Exception e) { |
| errorMessage = Messages.SaxMultiBugReportContentHandler_Bug_id_from_server_did_not_match_requested_id; |
| } |
| |
| TaskAttribute attr = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); |
| if (attr == null) { |
| attr = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag); |
| } |
| attr.setValue(parsedText); |
| |
| break; |
| } |
| |
| // Comment attributes |
| case WHO: |
| if (taskComment != null) { |
| taskComment.author = parsedText; |
| } |
| break; |
| case BUG_WHEN: |
| if (taskComment != null) { |
| taskComment.createdTimeStamp = parsedText; |
| } |
| break; |
| case WORK_TIME: |
| if (taskComment != null) { |
| taskComment.timeWorked = parsedText; |
| } |
| break; |
| case THETEXT: |
| if (taskComment != null) { |
| taskComment.commentText = parsedText; |
| } |
| break; |
| case LONG_DESC: |
| if (taskComment != null) { |
| longDescs.add(taskComment); |
| } |
| break; |
| |
| // Attachment attributes |
| case ATTACHID: |
| attachmentAttribute = repositoryTaskData.getRoot().createAttribute( |
| TaskAttribute.PREFIX_ATTACHMENT + parsedText); |
| attachment = TaskAttachmentMapper.createFrom(attachmentAttribute); |
| attachment.setLength(new Long(-1)); |
| attachment.setAttachmentId(parsedText); |
| attachment.setPatch(isPatch); |
| attachment.setDeprecated(isDeprecated); |
| break; |
| case DATE: |
| // ignore |
| break; |
| case DESC: |
| if (attachment != null) { |
| attachment.setDescription(parsedText); |
| } |
| break; |
| case FILENAME: |
| if (attachment != null) { |
| attachment.setFileName(parsedText); |
| } |
| break; |
| case CTYPE: |
| case TYPE: |
| if (attachment != null) { |
| attachment.setContentType(parsedText); |
| } |
| break; |
| case SIZE: |
| if (attachment != null) { |
| try { |
| if (parsedText != null) { |
| attachment.setLength(Long.parseLong(parsedText)); |
| } |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| break; |
| case ATTACHMENT: |
| if (attachment != null) { |
| attachment.applyTo(attachmentAttribute); |
| } |
| isPatch = false; |
| isDeprecated = false; |
| attachment = null; |
| attachmentAttribute = null; |
| break; |
| case DATA: |
| // ignored |
| break; |
| case BUGZILLA: |
| // ignored |
| break; |
| case BUG: |
| // Reached end of bug. |
| |
| addDescriptionAndComments(); |
| |
| // Need to set LONGDESCLENGTH to number of comments + 1 for description |
| TaskAttribute numCommentsAttribute = repositoryTaskData.getRoot().getMappedAttribute( |
| BugzillaAttribute.LONGDESCLENGTH.getKey()); |
| if (numCommentsAttribute == null) { |
| numCommentsAttribute = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, |
| BugzillaAttribute.LONGDESCLENGTH); |
| } |
| |
| numCommentsAttribute.setValue("" + commentNum); //$NON-NLS-1$ |
| |
| updateAttachmentMetaData(); |
| TaskAttribute attrCreation = repositoryTaskData.getRoot().getAttribute( |
| BugzillaAttribute.CREATION_TS.getKey()); |
| |
| updateCustomFields(repositoryTaskData); |
| |
| // Guard against empty data sets |
| if (attrCreation != null && !attrCreation.equals("")) { //$NON-NLS-1$ |
| collector.accept(repositoryTaskData); |
| } |
| break; |
| case BLOCKED: |
| // handled similarly to DEPENDSON |
| case DEPENDSON: |
| TaskAttribute blockOrDepends = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); |
| if (blockOrDepends == null) { |
| BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag).setValue(parsedText); |
| } else { |
| if (blockOrDepends.getValue().equals("")) { //$NON-NLS-1$ |
| blockOrDepends.setValue(parsedText); |
| } else { |
| blockOrDepends.setValue(blockOrDepends.getValue() + ", " + parsedText); //$NON-NLS-1$ |
| } |
| } |
| break; |
| case UNKNOWN: |
| //ignore |
| break; |
| case FLAG: |
| //ignore |
| break; |
| default: |
| TaskAttribute defaultAttribute = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); |
| if (defaultAttribute == null) { |
| defaultAttribute = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag); |
| defaultAttribute.setValue(parsedText); |
| } else { |
| defaultAttribute.addValue(parsedText); |
| } |
| break; |
| } |
| |
| } |
| |
| private void updateCustomFields(TaskData taskData) { |
| RepositoryConfiguration config = BugzillaCorePlugin.getRepositoryConfiguration(repositoryTaskData.getRepositoryUrl()); |
| if (config != null) { |
| for (BugzillaCustomField bugzillaCustomField : config.getCustomFields()) { |
| |
| TaskAttribute atr = taskData.getRoot().getAttribute(bugzillaCustomField.getName()); |
| if (atr == null) { |
| atr = taskData.getRoot().createAttribute(bugzillaCustomField.getName()); |
| } |
| |
| if (atr != null) { |
| atr.getMetaData().defaults().setLabel(bugzillaCustomField.getDescription()); |
| atr.getMetaData().setKind(TaskAttribute.KIND_DEFAULT); |
| |
| switch (bugzillaCustomField.getType()) { |
| case 1: // Free Text |
| atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); |
| break; |
| case 2: // Drop Down |
| atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); |
| break; |
| case 3: // Multiple-Selection Box |
| atr.getMetaData().setType(TaskAttribute.TYPE_MULTI_SELECT); |
| break; |
| case 4: // Large Text Box |
| atr.getMetaData().setType(TaskAttribute.TYPE_LONG_TEXT); |
| break; |
| case 5: // Date/Time |
| atr.getMetaData().setType(TaskAttribute.TYPE_DATETIME); |
| break; |
| |
| default: |
| List<String> options = bugzillaCustomField.getOptions(); |
| if (options.size() > 0) { |
| atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); |
| } else { |
| atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); |
| } |
| } |
| atr.getMetaData().setReadOnly(false); |
| } |
| } |
| } |
| |
| } |
| |
| private void updateAttachmentMetaData() { |
| List<TaskAttribute> taskAttachments = repositoryTaskData.getAttributeMapper().getAttributesByType( |
| repositoryTaskData, TaskAttribute.TYPE_ATTACHMENT); |
| for (TaskAttribute attachment : taskAttachments) { |
| TaskAttachmentMapper attachmentMapper = TaskAttachmentMapper.createFrom(attachment); |
| TaskCommentMapper taskComment = attachIdToComment.get(attachmentMapper.getAttachmentId()); |
| if (taskComment != null) { |
| attachmentMapper.setAuthor(taskComment.getAuthor()); |
| attachmentMapper.setCreationDate(taskComment.getCreationDate()); |
| } |
| attachmentMapper.setUrl(repositoryTaskData.getRepositoryUrl() |
| + IBugzillaConstants.URL_GET_ATTACHMENT_SUFFIX + attachmentMapper.getAttachmentId()); |
| attachmentMapper.applyTo(attachment); |
| } |
| } |
| |
| private void addDescriptionAndComments() { |
| int longDescsSize = longDescs.size() - 1; |
| commentNum = 1; |
| if (longDescsSize == 0) { |
| addDescription(longDescs.get(0).commentText); |
| } else if (longDescsSize == 1) { |
| if (longDescs.get(0).createdTimeStamp.compareTo(longDescs.get(1).createdTimeStamp) <= 0) { |
| // if created_0 is equal to created_1 we assume that longDescs at index 0 is the description. |
| addDescription(longDescs.get(0).commentText); |
| addComment(longDescs.get(1)); |
| } else { |
| addDescription(longDescs.get(1).commentText); |
| addComment(longDescs.get(0)); |
| } |
| } else if (longDescsSize > 1) { |
| String created_0 = longDescs.get(0).createdTimeStamp; |
| String created_1 = longDescs.get(1).createdTimeStamp; |
| String created_n = longDescs.get(longDescsSize).createdTimeStamp; |
| if (created_0.compareTo(created_1) <= 0 && created_0.compareTo(created_n) < 0) { |
| // if created_0 is equal to created_1 we assume that longDescs at index 0 is the description. |
| addDescription(longDescs.get(0).commentText); |
| |
| if (created_1.compareTo(created_n) < 0) { |
| for (int i = 1; i <= longDescsSize; i++) { |
| addComment(longDescs.get(i)); |
| } |
| } else { |
| for (int i = longDescsSize; i > 0; i--) { |
| addComment(longDescs.get(i)); |
| } |
| } |
| } else { |
| addDescription(longDescs.get(longDescsSize).commentText); |
| if (created_0.compareTo(created_1) < 0) { |
| for (int i = 0; i < longDescsSize; i++) { |
| addComment(longDescs.get(i)); |
| } |
| } else { |
| for (int i = longDescsSize - 1; i >= 0; i--) { |
| addComment(longDescs.get(i)); |
| } |
| } |
| } |
| } |
| } |
| |
| private void addDescription(String commentText) { |
| TaskAttribute attrDescription = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, |
| BugzillaAttribute.LONG_DESC); |
| attrDescription.setValue(commentText); |
| } |
| |
| private void addComment(TaskComment comment) { |
| TaskAttribute attribute = repositoryTaskData.getRoot().createAttribute( |
| TaskAttribute.PREFIX_COMMENT + commentNum); |
| TaskCommentMapper taskComment = TaskCommentMapper.createFrom(attribute); |
| taskComment.setCommentId(commentNum + ""); //$NON-NLS-1$ |
| taskComment.setNumber(commentNum); |
| IRepositoryPerson author = repositoryTaskData.getAttributeMapper().getTaskRepository().createPerson( |
| comment.author); |
| author.setName(comment.authorName); |
| taskComment.setAuthor(author); |
| TaskAttribute attrTimestamp = attribute.createAttribute(BugzillaAttribute.BUG_WHEN.getKey()); |
| attrTimestamp.setValue(comment.createdTimeStamp); |
| taskComment.setCreationDate(repositoryTaskData.getAttributeMapper().getDateValue(attrTimestamp)); |
| if (comment.commentText != null) { |
| String commentText = comment.commentText.trim(); |
| taskComment.setText(commentText); |
| |
| } |
| taskComment.applyTo(attribute); |
| commentNum++; |
| |
| if (comment.timeWorked != null) { |
| TaskAttribute workTime = BugzillaTaskDataHandler.createAttribute(attribute, BugzillaAttribute.WORK_TIME); |
| workTime.setValue(comment.timeWorked); |
| } |
| |
| parseAttachment(taskComment); |
| |
| } |
| |
| /** determines attachment id from comment */ |
| private void parseAttachment(TaskCommentMapper comment) { |
| String attachmentID = ""; //$NON-NLS-1$ |
| String commentText = comment.getText(); |
| int firstDelimiter = commentText.indexOf("\n"); //$NON-NLS-1$ |
| if (firstDelimiter < 0) { |
| firstDelimiter = commentText.length(); |
| } |
| int startIndex = commentText.indexOf(ID_STRING_BEGIN); |
| if (startIndex > 0 && startIndex < firstDelimiter) { |
| int endIndex = commentText.indexOf(ID_STRING_END, startIndex); |
| if (endIndex > 0 && endIndex < firstDelimiter) { |
| startIndex += ID_STRING_BEGIN.length(); |
| int p = startIndex; |
| while (p < endIndex) { |
| char c = commentText.charAt(p); |
| if (c < '0' || c > '9') { |
| break; |
| } |
| p++; |
| } |
| if (p == endIndex) { |
| attachmentID = commentText.substring(startIndex, endIndex); |
| if (!attachmentID.equals("")) { //$NON-NLS-1$ |
| attachIdToComment.put(attachmentID, comment); |
| } |
| } |
| } |
| } |
| } |
| |
| private static class TaskComment { |
| |
| public int number; |
| |
| public String author; |
| |
| public String authorName; |
| |
| public String createdTimeStamp; |
| |
| public String commentText; |
| |
| public String timeWorked; |
| |
| public boolean hasAttachment; |
| |
| public String attachmentId; |
| |
| public TaskComment(int num) { |
| this.number = num; |
| } |
| } |
| |
| } |