blob: 944ac42c563fdab9305a4e9eca67fc5540fa4e1e [file] [log] [blame]
/*******************************************************************************
* 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.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.net.Policy;
import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery;
import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.eclipse.mylyn.tasks.core.data.TaskRelation;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;
/**
* @author Mik Kersten
* @author Rob Elves
*/
public class BugzillaRepositoryConnector extends AbstractRepositoryConnector {
private static final String BUG_ID = "&bug_id="; //$NON-NLS-1$
private static final String CHANGED_BUGS_CGI_ENDDATE = "&chfieldto=Now"; //$NON-NLS-1$
private static final String CHANGED_BUGS_CGI_QUERY = "/buglist.cgi?query_format=advanced&chfieldfrom="; //$NON-NLS-1$
private static final String CLIENT_LABEL = Messages.BugzillaRepositoryConnector_BUGZILLA_SUPPORTS_2_18_TO_3_0;
private static final String COMMENT_FORMAT = "yyyy-MM-dd HH:mm"; //$NON-NLS-1$
private static final String DEADLINE_FORMAT = "yyyy-MM-dd"; //$NON-NLS-1$
private final BugzillaTaskAttachmentHandler attachmentHandler = new BugzillaTaskAttachmentHandler(this);
private final BugzillaTaskDataHandler taskDataHandler = new BugzillaTaskDataHandler(this);
protected BugzillaClientManager clientManager;
protected static BugzillaLanguageSettings enSetting;
protected final static Set<BugzillaLanguageSettings> languages = new LinkedHashSet<BugzillaLanguageSettings>();
@Override
public String getLabel() {
return CLIENT_LABEL;
}
@Override
public AbstractTaskAttachmentHandler getTaskAttachmentHandler() {
return attachmentHandler;
}
@Override
public String getConnectorKind() {
return BugzillaCorePlugin.CONNECTOR_KIND;
}
@Override
public void updateTaskFromTaskData(TaskRepository repository, ITask task, TaskData taskData) {
TaskMapper scheme = new TaskMapper(taskData);
scheme.applyTo(task);
task.setUrl(BugzillaClient.getBugUrlWithoutLogin(repository.getRepositoryUrl(), taskData.getTaskId()));
boolean isComplete = false;
TaskAttribute attributeStatus = taskData.getRoot().getMappedAttribute(TaskAttribute.STATUS);
if (attributeStatus != null) {
isComplete = attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_RESOLVED)
|| attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_CLOSED)
|| attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_VERIFIED);
}
if (taskData.isPartial()) {
if (isComplete) {
if (task.getCompletionDate() == null) {
task.setCompletionDate(new Date(0));
}
} else {
if (task.getCompletionDate() != null) {
task.setCompletionDate(null);
}
}
} else {
// Completion Date
if (isComplete) {
Date completionDate = null;
List<TaskAttribute> taskComments = taskData.getAttributeMapper().getAttributesByType(taskData,
TaskAttribute.TYPE_COMMENT);
if (taskComments != null && taskComments.size() > 0) {
TaskAttribute lastComment = taskComments.get(taskComments.size() - 1);
if (lastComment != null) {
TaskAttribute attributeCommentDate = lastComment.getMappedAttribute(TaskAttribute.COMMENT_DATE);
if (attributeCommentDate != null) {
try {
completionDate = new SimpleDateFormat(COMMENT_FORMAT).parse(attributeCommentDate.getValue());
} catch (ParseException e) {
// ignore
}
}
}
}
if (completionDate == null) {
// Use last modified date
TaskAttribute attributeLastModified = taskData.getRoot().getMappedAttribute(
TaskAttribute.DATE_MODIFICATION);
if (attributeLastModified != null && attributeLastModified.getValue().length() > 0) {
completionDate = taskData.getAttributeMapper().getDateValue(attributeLastModified);
}
}
if (completionDate == null) {
completionDate = new Date();
}
task.setCompletionDate(completionDate);
} else {
task.setCompletionDate(null);
}
// Bugzilla Specific Attributes
// Product
if (scheme.getProduct() != null) {
task.setAttribute(TaskAttribute.PRODUCT, scheme.getProduct());
}
// Severity
TaskAttribute attrSeverity = taskData.getRoot().getMappedAttribute(BugzillaAttribute.BUG_SEVERITY.getKey());
if (attrSeverity != null && !attrSeverity.getValue().equals("")) { //$NON-NLS-1$
task.setAttribute(BugzillaAttribute.BUG_SEVERITY.getKey(), attrSeverity.getValue());
}
// Due Date
if (taskData.getRoot().getMappedAttribute(BugzillaAttribute.ESTIMATED_TIME.getKey()) != null) {
Date dueDate = null;
// HACK: if estimated_time field exists, time tracking is
// enabled
try {
TaskAttribute attributeDeadline = taskData.getRoot().getMappedAttribute(
BugzillaAttribute.DEADLINE.getKey());
if (attributeDeadline != null) {
dueDate = new SimpleDateFormat(DEADLINE_FORMAT).parse(attributeDeadline.getValue());
}
} catch (Exception e) {
// ignore
}
task.setDueDate(dueDate);
}
}
updateExtendedAttributes(task, taskData);
}
private void updateExtendedAttributes(ITask task, TaskData taskData) {
// Set meta bugzilla task attribute values
for (BugzillaAttribute bugzillaReportElement : BugzillaAttribute.EXTENDED_ATTRIBUTES) {
TaskAttribute taskAttribute = taskData.getRoot().getAttribute(bugzillaReportElement.getKey());
if (taskAttribute != null) {
task.setAttribute(bugzillaReportElement.getKey(), taskAttribute.getValue());
}
}
}
@Override
public void preSynchronization(ISynchronizationSession session, IProgressMonitor monitor) throws CoreException {
TaskRepository repository = session.getTaskRepository();
if (session.getTasks().isEmpty()) {
return;
}
monitor = Policy.monitorFor(monitor);
try {
monitor.beginTask(Messages.BugzillaRepositoryConnector_checking_for_changed_tasks, session.getTasks()
.size());
if (repository.getSynchronizationTimeStamp() == null) {
for (ITask task : session.getTasks()) {
session.markStale(task);
}
return;
}
String dateString = repository.getSynchronizationTimeStamp();
if (dateString == null) {
dateString = ""; //$NON-NLS-1$
}
String urlQueryBase = repository.getRepositoryUrl() + CHANGED_BUGS_CGI_QUERY
+ URLEncoder.encode(dateString, repository.getCharacterEncoding()) + CHANGED_BUGS_CGI_ENDDATE;
String urlQueryString = urlQueryBase + BUG_ID;
Set<ITask> changedTasks = new HashSet<ITask>();
Iterator<ITask> itr = session.getTasks().iterator();
int queryCounter = 0;
Set<ITask> checking = new HashSet<ITask>();
while (itr.hasNext()) {
ITask task = itr.next();
checking.add(task);
queryCounter++;
String newurlQueryString = URLEncoder.encode(task.getTaskId() + ",", repository.getCharacterEncoding()); //$NON-NLS-1$
urlQueryString += newurlQueryString;
if (queryCounter >= 1000) {
queryForChanged(repository, changedTasks, urlQueryString, session, new SubProgressMonitor(monitor,
queryCounter));
queryCounter = 0;
urlQueryString = urlQueryBase + BUG_ID;
newurlQueryString = ""; //$NON-NLS-1$
}
if (!itr.hasNext() && queryCounter != 0) {
queryForChanged(repository, changedTasks, urlQueryString, session, new SubProgressMonitor(monitor,
queryCounter));
}
}
for (ITask task : session.getTasks()) {
if (changedTasks.contains(task)) {
session.markStale(task);
}
}
} catch (UnsupportedEncodingException e) {
throw new CoreException(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN,
"Repository configured with unsupported encoding: " + repository.getCharacterEncoding() //$NON-NLS-1$
+ "\n\n Unable to determine changed tasks.", e)); //$NON-NLS-1$
} finally {
monitor.done();
}
}
/**
* TODO: clean up use of BugzillaTaskDataCollector
*/
private void queryForChanged(final TaskRepository repository, Set<ITask> changedTasks, String urlQueryString,
ISynchronizationSession context, IProgressMonitor monitor) throws UnsupportedEncodingException,
CoreException {
HashMap<String, ITask> taskById = new HashMap<String, ITask>();
for (ITask task : context.getTasks()) {
taskById.put(task.getTaskId(), task);
}
final Set<TaskData> changedTaskData = new HashSet<TaskData>();
TaskDataCollector collector = new TaskDataCollector() {
@Override
public void accept(TaskData taskData) {
changedTaskData.add(taskData);
}
};
// TODO: Decouple from internals
IRepositoryQuery query = new RepositoryQuery(repository.getConnectorKind(), ""); //$NON-NLS-1$
query.setSummary(Messages.BugzillaRepositoryConnector_Query_for_changed_tasks);
query.setUrl(urlQueryString);
performQuery(repository, query, collector, context, monitor);
for (TaskData data : changedTaskData) {
ITask changedTask = taskById.get(data.getTaskId());
if (changedTask != null) {
changedTasks.add(changedTask);
}
}
}
@Override
public boolean canCreateTaskFromKey(TaskRepository repository) {
return true;
}
@Override
public boolean canCreateNewTask(TaskRepository repository) {
return true;
}
@Override
public IStatus performQuery(TaskRepository repository, final IRepositoryQuery query,
TaskDataCollector resultCollector, ISynchronizationSession event, IProgressMonitor monitor) {
monitor = Policy.monitorFor(monitor);
try {
monitor.beginTask(Messages.BugzillaRepositoryConnector_running_query, IProgressMonitor.UNKNOWN);
BugzillaClient client = getClientManager().getClient(repository, new SubProgressMonitor(monitor, 1));
TaskAttributeMapper mapper = getTaskDataHandler().getAttributeMapper(repository);
boolean hitsReceived = client.getSearchHits(query, resultCollector, mapper, monitor);
if (!hitsReceived) {
// XXX: HACK in case of ip change bugzilla can return 0 hits
// due to invalid authorization token, forcing relogin fixes
client.logout(monitor);
client.getSearchHits(query, resultCollector, mapper, monitor);
}
return Status.OK_STATUS;
} catch (UnrecognizedReponseException e) {
return new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, IStatus.INFO,
Messages.BugzillaRepositoryConnector_Unrecognized_response_from_server, e);
} catch (IOException e) {
return new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, IStatus.ERROR, MessageFormat.format(
Messages.BugzillaRepositoryConnector_Check_repository_configuration, e.getMessage()), e);
} catch (CoreException e) {
return e.getStatus();
} finally {
monitor.done();
}
}
@Override
public String getRepositoryUrlFromTaskUrl(String url) {
if (url == null) {
return null;
}
int index = url.indexOf(IBugzillaConstants.URL_GET_SHOW_BUG);
return index == -1 ? null : url.substring(0, index);
}
@Override
public String getTaskIdFromTaskUrl(String url) {
if (url == null) {
return null;
}
int anchorIndex = url.lastIndexOf("#"); //$NON-NLS-1$
String bugUrl = url;
if (anchorIndex != -1) {
bugUrl = url.substring(0, anchorIndex);
}
int index = bugUrl.indexOf(IBugzillaConstants.URL_GET_SHOW_BUG);
return index == -1 ? null : bugUrl.substring(index + IBugzillaConstants.URL_GET_SHOW_BUG.length());
}
@Override
public String getTaskUrl(String repositoryUrl, String taskId) {
try {
return BugzillaClient.getBugUrlWithoutLogin(repositoryUrl, taskId);
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN,
"Error constructing task url for " + repositoryUrl + " id:" + taskId, e)); //$NON-NLS-1$ //$NON-NLS-2$
}
return null;
}
@Override
public String getTaskIdPrefix() {
return "bug"; //$NON-NLS-1$
}
public BugzillaClientManager getClientManager() {
if (clientManager == null) {
clientManager = new BugzillaClientManager();
// TODO: Move this initialization elsewhere
BugzillaCorePlugin.setConnector(this);
enSetting = new BugzillaLanguageSettings(IBugzillaConstants.DEFAULT_LANG);
enSetting.addLanguageAttribute("error_login", "Login"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_login", "log in"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_login", "check e-mail"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_login", "Invalid Username Or Password"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_collision", "Mid-air collision!"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_comment_required", "Comment Required"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_logged_out", "logged out"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bad_login", "Login"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bad_login", "log in"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bad_login", "check e-mail"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bad_login", "Invalid Username Or Password"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bad_login", "error"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("processed", "processed"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("changes_submitted", "Changes submitted"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("changes_submitted", "added to Bug"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bug", "Bug"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("bug", "Issue"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("submitted", "Submitted"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("submitted", "posted"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("suspicious_action", "Suspicious action"); //$NON-NLS-1$ //$NON-NLS-2$
languages.add(enSetting);
}
return clientManager;
}
@Override
public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor) throws CoreException {
if (repository != null) {
BugzillaCorePlugin.getRepositoryConfiguration(repository, true, monitor);
}
}
@Override
public boolean isRepositoryConfigurationStale(TaskRepository repository, IProgressMonitor monitor)
throws CoreException {
if (super.isRepositoryConfigurationStale(repository, monitor)) {
boolean result = true;
BugzillaClient client = getClientManager().getClient(repository, monitor);
if (client != null) {
String timestamp = client.getConfigurationTimestamp(monitor);
if (timestamp != null) {
String oldTimestamp = repository.getProperty(IBugzillaConstants.PROPERTY_CONFIGTIMESTAMP);
if (oldTimestamp != null) {
result = !timestamp.equals(oldTimestamp);
}
repository.setProperty(IBugzillaConstants.PROPERTY_CONFIGTIMESTAMP, timestamp);
}
}
return result;
}
return false;
}
public static void addLanguageSetting(BugzillaLanguageSettings language) {
if (!languages.contains(language)) {
BugzillaRepositoryConnector.languages.add(language);
}
}
public static Set<BugzillaLanguageSettings> getLanguageSettings() {
return languages;
}
/** returns default language if language not found */
public static BugzillaLanguageSettings getLanguageSetting(String label) {
for (BugzillaLanguageSettings language : getLanguageSettings()) {
if (language.getLanguageName().equals(label)) {
return language;
}
}
return enSetting;
}
@Override
public void postSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask("", 1); //$NON-NLS-1$
if (event.isFullSynchronization() && event.getStatus() == null) {
event.getTaskRepository().setSynchronizationTimeStamp(getSynchronizationTimestamp(event));
}
} finally {
monitor.done();
}
}
@Override
public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor)
throws CoreException {
return taskDataHandler.getTaskData(repository, taskId, monitor);
}
@Override
public AbstractTaskDataHandler getTaskDataHandler() {
return taskDataHandler;
}
@Override
public boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData) {
if (taskData.isPartial() && task.getCreationDate() != null) {
return false;
}
// Security token
// Updated on the task upon each open (synch) to keep the most current token available for submission - bug#263318
TaskAttribute attrSecurityToken = taskData.getRoot().getMappedAttribute(BugzillaAttribute.TOKEN.getKey());
if (attrSecurityToken != null && !attrSecurityToken.getValue().equals("")) { //$NON-NLS-1$
task.setAttribute(BugzillaAttribute.TOKEN.getKey(), attrSecurityToken.getValue());
}
String lastKnownMod = task.getAttribute(BugzillaAttribute.DELTA_TS.getKey());
if (lastKnownMod != null) {
TaskAttribute attrModification = taskData.getRoot().getMappedAttribute(TaskAttribute.DATE_MODIFICATION);
if (attrModification != null) {
return !lastKnownMod.equals(attrModification.getValue());
}
}
return true;
}
@Override
public Collection<TaskRelation> getTaskRelations(TaskData taskData) {
List<TaskRelation> relations = new ArrayList<TaskRelation>();
TaskAttribute attribute = taskData.getRoot().getAttribute(BugzillaAttribute.DEPENDSON.getKey());
if (attribute != null && attribute.getValue().length() > 0) {
for (String taskId : attribute.getValue().split(",")) { //$NON-NLS-1$
relations.add(TaskRelation.subtask(taskId.trim()));
}
}
return relations;
}
private String getSynchronizationTimestamp(ISynchronizationSession event) {
Date mostRecent = new Date(0);
String mostRecentTimeStamp = event.getTaskRepository().getSynchronizationTimeStamp();
for (ITask task : event.getChangedTasks()) {
Date taskModifiedDate = task.getModificationDate();
if (taskModifiedDate != null && taskModifiedDate.after(mostRecent)) {
mostRecent = taskModifiedDate;
mostRecentTimeStamp = task.getAttribute(BugzillaAttribute.DELTA_TS.getKey());
}
}
return mostRecentTimeStamp;
}
@Override
public boolean hasRepositoryDueDate(TaskRepository taskRepository, ITask task, TaskData taskData) {
return taskData.getRoot().getAttribute(BugzillaAttribute.ESTIMATED_TIME.getKey()) != null;
}
}