blob: 2743ee7f275bf9b818d7610c83bded21fc319d8d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2014 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.bugzilla.core;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.Map;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
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.AbstractTask;
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.ITask.PriorityLevel;
import org.eclipse.mylyn.tasks.core.RepositoryStatus;
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.TaskHistory;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.eclipse.mylyn.tasks.core.data.TaskRelation;
import org.eclipse.mylyn.tasks.core.data.TaskRevision;
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_CONNECTOR_LABEL;
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 static final String TIMESTAMP_WITH_OFFSET = "yyyy-MM-dd HH:mm:ss Z"; //$NON-NLS-1$
private static final long HOUR = 1000 * 60 * 60;
private final BugzillaTaskAttachmentHandler attachmentHandler = new BugzillaTaskAttachmentHandler(this);
private final BugzillaTaskDataHandler taskDataHandler = new BugzillaTaskDataHandler(this);
protected BugzillaClientManager clientManager;
protected static BugzillaLanguageSettings enSetting;
protected static final Set<BugzillaLanguageSettings> languages = new LinkedHashSet<BugzillaLanguageSettings>();
private static final String ERROR_DELETING_CONFIGURATION = "Error removing corrupt repository configuration file."; //$NON-NLS-1$
private static final String ERROR_INCOMPATIBLE_CONFIGURATION = "Reset Bugzilla repository configuration cache due to format change"; //$NON-NLS-1$
private boolean cacheFileRead;
private File repositoryConfigurationFile;
private final Map<String, RepositoryConfiguration> repositoryConfigurations = new HashMap<String, RepositoryConfiguration>();
// A Map from Java's Platform to Buzilla's
private static final Map<String, String> java2buzillaPlatformMap = new HashMap<String, String>();
static {
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_login", "account locked"); //$NON-NLS-1$//$NON-NLS-2$
enSetting.addLanguageAttribute("error_collision", "Mid-air collision!"); //$NON-NLS-1$ //$NON-NLS-2$
enSetting.addLanguageAttribute("error_collision", "Mid-air collision detected!"); //$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", "account locked"); //$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$
enSetting.addLanguageAttribute("error_confirm_match", "confirm match"); //$NON-NLS-1$//$NON-NLS-2$
enSetting.addLanguageAttribute("error_match_failed", "match failed"); //$NON-NLS-1$ //$NON-NLS-2$
languages.add(enSetting);
java2buzillaPlatformMap.put("x86", "PC"); // can be PC or Macintosh! //$NON-NLS-1$ //$NON-NLS-2$
java2buzillaPlatformMap.put("x86_64", "PC"); //$NON-NLS-1$ //$NON-NLS-2$
java2buzillaPlatformMap.put("ia64", "PC"); //$NON-NLS-1$ //$NON-NLS-2$
java2buzillaPlatformMap.put("ia64_32", "PC"); //$NON-NLS-1$ //$NON-NLS-2$
java2buzillaPlatformMap.put("sparc", "Sun"); //$NON-NLS-1$ //$NON-NLS-2$
java2buzillaPlatformMap.put("ppc", "Power PC"); // not Power! //$NON-NLS-1$ //$NON-NLS-2$
}
public BugzillaRepositoryConnector() {
if (BugzillaCorePlugin.getDefault() != null) {
BugzillaCorePlugin.getDefault().setConnector(this);
IPath path = BugzillaCorePlugin.getDefault().getConfigurationCachePath();
this.repositoryConfigurationFile = path.toFile();
}
}
public BugzillaRepositoryConnector(File repositoryConfigurationFile) {
this.repositoryConfigurationFile = repositoryConfigurationFile;
}
@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 = getTaskMapping(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) {
RepositoryConfiguration configuration = getRepositoryConfiguration(repository.getRepositoryUrl());
if (configuration == null || configuration.getClosedStatusValues().isEmpty()) {
isComplete = attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_RESOLVED)
|| attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_CLOSED)
|| attributeStatus.getValue().equals(IBugzillaConstants.VALUE_STATUS_VERIFIED);
} else {
isComplete = configuration.getClosedStatusValues().contains(attributeStatus.getValue());
}
}
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());
}
}
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;
StringBuilder urlQueryString = new StringBuilder(Math.min(30 + 9 * session.getTasks().size(), 7009));
urlQueryString.append(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++;
urlQueryString.append(URLEncoder.encode(task.getTaskId() + ",", repository.getCharacterEncoding())); //$NON-NLS-1$
if (urlQueryString.length() >= 7000) {
queryForChanged(repository, changedTasks, urlQueryString.toString(), session,
new SubProgressMonitor(monitor, queryCounter));
queryCounter = 0;
urlQueryString.setLength(0);
urlQueryString.append(urlQueryBase + BUG_ID);
}
if (!itr.hasNext() && queryCounter != 0) {
queryForChanged(repository, changedTasks, urlQueryString.toString(), 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.", //$NON-NLS-1$
e));
} finally {
monitor.done();
}
}
private void queryForChanged(final TaskRepository repository, Set<ITask> changedTasks, String urlQueryString,
ISynchronizationSession syncSession, IProgressMonitor monitor)
throws UnsupportedEncodingException, CoreException {
HashMap<String, ITask> taskById = new HashMap<String, ITask>();
for (ITask task : syncSession.getTasks()) {
taskById.put(task.getTaskId(), task);
}
BugzillaTaskDataCollector collector = new BugzillaTaskDataCollector();
// 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, syncSession, monitor);
for (TaskData data : collector.getTaskData()) {
ITask changedTask = taskById.get(data.getTaskId());
if (changedTask != null) {
changedTasks.add(changedTask);
}
}
if (syncSession.getData() == null && collector.getQueryTimestamp() != null) {
// Bugzilla 4.2 does not parse the timezone of the time stamp properly hence it needs to be persisted in
// server time and not local time
syncSession.setData(collector.getQueryTimestamp());
// Date queryDate = BugzillaAttributeMapper.parseDate(collector.getQueryTimestamp());
// if (queryDate != null) {
// // Ensure time is in right format
// syncSession.setData(new SimpleDateFormat(TIMESTAMP_WITH_OFFSET).format(queryDate));
// }
}
}
@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(this);
}
return clientManager;
}
@Override
public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor)
throws CoreException {
if (repository != null) {
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 {
monitor = Policy.monitorFor(monitor);
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 && attrModification.getValue() != null
&& attrModification.getValue().length() > 0) {
boolean cachedHasTZ = hasTimzone(lastKnownMod);
boolean repoHasTZ = hasTimzone(attrModification.getValue());
if (!cachedHasTZ && !repoHasTZ) { // State 1
return !lastKnownMod.equals(attrModification.getValue());
}
BugzillaAttributeMapper mapper = (BugzillaAttributeMapper) taskData.getAttributeMapper();
Date oldModDate = BugzillaAttributeMapper.parseDate(lastKnownMod);
Date newModDate = mapper.getDateValue(attrModification);
// If either of the dates can't be parsed, fall back to string comparison
if (oldModDate == null) {
((AbstractTask) task).setStatus(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN,
"Unable to parse cached task modification timestamp " + lastKnownMod)); //$NON-NLS-1$
return !lastKnownMod.equals(attrModification.getValue());
} else if (newModDate == null) {
((AbstractTask) task).setStatus(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN,
"Unable to parse incoming task modification timestamp " + attrModification.getValue())); //$NON-NLS-1$
return !lastKnownMod.equals(attrModification.getValue());
}
if ((cachedHasTZ && !repoHasTZ) || (!cachedHasTZ && repoHasTZ)) { // State 2 (unlikely) || Sate 3
long delta = Math.abs(newModDate.getTime() - oldModDate.getTime());
if (delta == 0) {
return false;
} else if (delta > 0 && delta % HOUR == 0 && delta < 24 * HOUR) {
// If same time but in different time zones, ignore/migrate
return false;
}
return true;
} else if (cachedHasTZ && repoHasTZ) { //State 4 (of 4)
// Date Compare
return oldModDate.compareTo(newModDate) != 0;
}
}
}
return true;
}
private boolean hasTimzone(String dateString) {
if (dateString == null || dateString.length() == 0) {
return false;
}
String[] parts = dateString.split(" "); //$NON-NLS-1$
boolean hasTimeZone = (parts != null && parts.length == 3);
return hasTimeZone;
}
@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()));
}
}
attribute = taskData.getRoot().getAttribute(BugzillaAttribute.BLOCKED.getKey());
if (attribute != null && attribute.getValue().length() > 0) {
for (String taskId : attribute.getValue().split(",")) { //$NON-NLS-1$
relations.add(TaskRelation.parentTask(taskId.trim()));
}
}
return relations;
}
private String getSynchronizationTimestamp(ISynchronizationSession event) {
Date mostRecent = new Date(0);
String mostRecentTimeStamp = event.getTaskRepository().getSynchronizationTimeStamp();
if (event.getData() != null) {
mostRecentTimeStamp = (String) event.getData();
} else {
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;
}
@Override
public TaskMapper getTaskMapping(final TaskData taskData) {
return new TaskMapper(taskData) {
@Override
public String getTaskKey() {
TaskAttribute attribute = getTaskData().getRoot().getAttribute(BugzillaAttribute.BUG_ID.getKey());
if (attribute != null) {
return attribute.getValue();
}
return super.getTaskKey();
}
@Override
public String getTaskKind() {
return taskData.getConnectorKind();
}
@Override
public String getTaskUrl() {
return taskData.getRepositoryUrl();
}
@Override
public PriorityLevel getPriorityLevel() {
RepositoryConfiguration repositoryConfiguration = BugzillaRepositoryConnector.this
.getRepositoryConfiguration(taskData.getRepositoryUrl());
String priority = getPriority();
if (repositoryConfiguration == null) {
// When we did not have the configuration we only can use the standard Priorities
// Did not know how the configuration can be null here
return getTaskPriority(priority);
}
List<String> priorities = repositoryConfiguration.getOptionValues(BugzillaAttribute.PRIORITY);
return BugzillaRepositoryConnector.getTaskPriority(priority, priorities);
}
};
}
/** public for testing */
public synchronized void readRepositoryConfigurationFile() {
if (cacheFileRead || repositoryConfigurationFile == null || !repositoryConfigurationFile.exists()) {
return;
}
synchronized (repositoryConfigurations) {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(repositoryConfigurationFile));
int size = in.readInt();
for (int nX = 0; nX < size; nX++) {
RepositoryConfiguration item = (RepositoryConfiguration) in.readObject();
if (item != null) {
repositoryConfigurations.put(item.getRepositoryUrl(), item);
}
}
} catch (Exception e) {
StatusHandler
.log(new Status(IStatus.INFO, BugzillaCorePlugin.ID_PLUGIN, ERROR_INCOMPATIBLE_CONFIGURATION));
try {
if (in != null) {
in.close();
}
if (repositoryConfigurationFile != null && repositoryConfigurationFile.exists()) {
if (repositoryConfigurationFile.delete()) {
// successfully deleted
} else {
StatusHandler.log(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, 0,
ERROR_DELETING_CONFIGURATION, e));
}
}
} catch (Exception ex) {
StatusHandler.log(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, 0,
ERROR_DELETING_CONFIGURATION, e));
}
} finally {
cacheFileRead = true;
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
/**
* Retrieves the latest repository configuration from the server
*/
public RepositoryConfiguration getRepositoryConfiguration(TaskRepository repository, boolean forceRefresh,
IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
readRepositoryConfigurationFile();
RepositoryConfiguration configuration;
configuration = repositoryConfigurations.get(repository.getRepositoryUrl());
if (configuration == null || forceRefresh) {
synchronized (repositoryConfigurations) {
// check if another thread already retrieved configuration
configuration = repositoryConfigurations.get(repository.getRepositoryUrl());
if (configuration == null || forceRefresh) {
String eTag = null;
Date lastModifiedHeader = null;
if (configuration != null && !forceRefresh) {
eTag = configuration.getETagValue();
lastModifiedHeader = configuration.getLastModifiedHeader();
}
BugzillaClient client = getClientManager().getClient(repository, monitor);
configuration = client.getRepositoryConfiguration(monitor, eTag);
boolean newer = true;
if (configuration != null) {
if (lastModifiedHeader != null) {
Date configLastModifiedHeader = configuration.getLastModifiedHeader();
if (configLastModifiedHeader != null) {
newer = !configLastModifiedHeader.before(lastModifiedHeader);
}
}
if (newer) {
String configVersion = configuration.getInstallVersion().toString();
String repositoryVersion = repository.getVersion();
if (!configVersion.equals(repositoryVersion)) {
repository.setVersion(configVersion);
}
internalAddConfiguration(configuration);
}
}
}
}
}
return configuration;
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, 1,
"Error retrieving task attributes from repository.\n\n" + e.getMessage(), e)); //$NON-NLS-1$
} catch (CoreException e) {
if (e.getMessage().equals("Not changed")) { //$NON-NLS-1$
RepositoryConfiguration configuration = repositoryConfigurations.get(repository.getRepositoryUrl());
if (configuration == null) {
throw new CoreException(new BugzillaStatus(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN,
RepositoryStatus.ERROR_INTERNAL, "Failed to retrieve repository configuration for " //$NON-NLS-1$
+ repository.getRepositoryUrl().toString()));
}
return configuration;
} else {
throw e;
}
}
}
public void addRepositoryConfiguration(RepositoryConfiguration config) {
if (config != null) {
readRepositoryConfigurationFile();
synchronized (repositoryConfigurations) {
internalAddConfiguration(config);
}
}
}
private void internalAddConfiguration(RepositoryConfiguration config) {
repositoryConfigurations.remove(config.getRepositoryUrl());
repositoryConfigurations.put(config.getRepositoryUrl(), config);
}
public RepositoryConfiguration getRepositoryConfiguration(String repositoryUrl) {
readRepositoryConfigurationFile();
return repositoryConfigurations.get(repositoryUrl);
}
/** public for testing */
public void removeConfiguration(RepositoryConfiguration config) {
synchronized (repositoryConfigurations) {
repositoryConfigurations.remove(config.getRepositoryUrl());
}
}
/** public for testing */
public void writeRepositoryConfigFile() {
if (repositoryConfigurationFile != null) {
try {
Set<RepositoryConfiguration> tempConfigs;
synchronized (repositoryConfigurations) {
tempConfigs = new HashSet<RepositoryConfiguration>(repositoryConfigurations.values());
}
if (tempConfigs.size() > 0) {
try (ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(repositoryConfigurationFile)))) {
out.writeInt(tempConfigs.size());
for (RepositoryConfiguration repositoryConfiguration : tempConfigs) {
if (repositoryConfiguration != null) {
out.writeObject(repositoryConfiguration);
}
}
}
}
} catch (IOException e) {
StatusHandler.log(new Status(IStatus.WARNING, BugzillaCorePlugin.ID_PLUGIN, 0,
"Failed to write repository configuration cache", e)); //$NON-NLS-1$
}
}
}
public void stop() {
writeRepositoryConfigFile();
}
public void setPlatformDefaultsOrGuess(TaskRepository repository, TaskData newBugModel) {
String platform = repository.getProperty(IBugzillaConstants.BUGZILLA_DEF_PLATFORM);
String os = repository.getProperty(IBugzillaConstants.BUGZILLA_DEF_OS);
// set both or none
if (null != os && null != platform) {
TaskAttribute opSysAttribute = newBugModel.getRoot().getAttribute(BugzillaAttribute.OP_SYS.getKey());
TaskAttribute platformAttribute = newBugModel.getRoot()
.getAttribute(BugzillaAttribute.REP_PLATFORM.getKey());
// TODO something can still go wrong when the allowed values on the repository change...
opSysAttribute.setValue(os);
platformAttribute.setValue(platform);
return;
}
// fall through to old code
setPlatformOptions(newBugModel);
}
public void setPlatformOptions(TaskData newBugModel) {
try {
// Get OS Lookup Map
// Check that the result is in Values, if it is not, set it to other
// Defaults to the first of each (sorted) list All, All
TaskAttribute opSysAttribute = newBugModel.getRoot().getAttribute(BugzillaAttribute.OP_SYS.getKey());
TaskAttribute platformAttribute = newBugModel.getRoot()
.getAttribute(BugzillaAttribute.REP_PLATFORM.getKey());
String OS = Platform.getOS();
String platform = Platform.getOSArch();
String ws = Platform.getWS();
String bugzillaOS = null; // Bugzilla String for OS
String bugzillaPlatform = null; // Bugzilla String for Platform
String[] wsExtentions = null;
/*
AIX -> AIX
Linux -> Linux
HP-UX -> HP-UX
Solaris -> Solaris
MacOS X -> Mac OS X
*/
if (ws.length() > 1) {
char first = ws.charAt(0);
char firstLower = Character.toLowerCase(first);
char firstUpper = Character.toUpperCase(first);
String[] wsExtentionsTemp = { " - " + firstUpper + ws.substring(1, ws.length()), //$NON-NLS-1$
" - " + firstLower + ws.substring(1, ws.length()), //$NON-NLS-1$
" " + firstUpper + ws.substring(1, ws.length()), //$NON-NLS-1$
" " + firstLower + ws.substring(1, ws.length()), "" }; //$NON-NLS-1$//$NON-NLS-2$
wsExtentions = wsExtentionsTemp;
} else if (ws.length() == 1) {
char first = ws.charAt(0);
char firstLower = Character.toLowerCase(first);
char firstUpper = Character.toUpperCase(first);
String[] wsExtentionsTemp = { " - " + firstUpper, " - " + firstLower, " " + firstUpper, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
" " + firstLower, "" }; //$NON-NLS-1$//$NON-NLS-2$
wsExtentions = wsExtentionsTemp;
} else {
String[] wsExtentionsTemp = { "" }; //$NON-NLS-1$
wsExtentions = wsExtentionsTemp;
}
bugzillaOS = System.getProperty("os.name") + " " + System.getProperty("os.version"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// We start with the most specific Value as the Search String.
// If we didn't find it we remove the last part of the version String or the OS Name from
// the Search String and continue with the test until we found it or the Search String is empty.
//
// The search in casesensitive.
if (opSysAttribute != null) {
for (String element : wsExtentions) {
String bugzillaOSTemp = bugzillaOS;
while (bugzillaOSTemp != null && opSysAttribute.getOption(bugzillaOSTemp + element) == null) {
int dotindex = bugzillaOSTemp.lastIndexOf('.');
if (dotindex > 0) {
bugzillaOSTemp = bugzillaOSTemp.substring(0, dotindex);
} else {
int spaceindex = bugzillaOSTemp.lastIndexOf(' ');
if (spaceindex > 0) {
bugzillaOSTemp = bugzillaOSTemp.substring(0, spaceindex);
} else {
bugzillaOSTemp = null;
}
}
}
if (bugzillaOSTemp != null) {
bugzillaOS = bugzillaOSTemp + element;
break;
}
}
} else {
bugzillaOS = null;
}
if (platform != null && java2buzillaPlatformMap.containsKey(platform)) {
bugzillaPlatform = java2buzillaPlatformMap.get(platform);
// Bugzilla knows the following Platforms [All, Macintosh, Other, PC, Power PC, Sun]
// Platform.getOSArch() returns "x86" on Intel Mac's and "ppc" on Power Mac's
// so bugzillaPlatform is "Power" or "PC".
//
// If the OS is "macosx" we change the Platform to "Macintosh"
//
if (bugzillaPlatform != null
&& (bugzillaPlatform.compareTo("Power") == 0 || bugzillaPlatform.compareTo("PC") == 0) //$NON-NLS-1$ //$NON-NLS-2$
&& OS != null && OS.compareTo("macosx") == 0) { //$NON-NLS-1$
// TODO: this may not even be a legal value in another repository!
bugzillaPlatform = "Macintosh"; //$NON-NLS-1$
} else if (platformAttribute != null && platformAttribute.getOption(bugzillaPlatform) == null) {
// If the platform we found is not int the list of available
// optinos, set the
// Bugzilla Platform to null, and juse use "other"
bugzillaPlatform = null;
}
}
// Set the OS and the Platform in the taskData
if (bugzillaOS != null && opSysAttribute != null && opSysAttribute.getOption(bugzillaOS) != null) {
opSysAttribute.setValue(bugzillaOS);
}
/*else if (opSysAttribute != null && opSysAttribute.getOption(OPTION_ALL) != null) {
opSysAttribute.setValue(OPTION_ALL);
}*/
if (bugzillaPlatform != null && platformAttribute != null
&& platformAttribute.getOption(bugzillaPlatform) != null) {
platformAttribute.setValue(bugzillaPlatform);
}
/*else if (opSysAttribute != null && platformAttribute != null
&& platformAttribute.getOption(OPTION_ALL) != null) {
opSysAttribute.setValue(OPTION_ALL);
}*/
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.ERROR, BugzillaCorePlugin.ID_PLUGIN, "could not set platform options", //$NON-NLS-1$
e));
}
}
public enum BugzillaPriorityLevel {
HIGHEST, HIGH, NORMAL, LOW, LOWEST, NONE;
public static BugzillaPriorityLevel fromPriority(String priority) {
if (priority == null) {
return null;
}
if (priority.equals("Highest")) { //$NON-NLS-1$
return HIGHEST;
}
if (priority.equals("High")) { //$NON-NLS-1$
return HIGH;
}
if (priority.equals("Normal")) { //$NON-NLS-1$
return NORMAL;
}
if (priority.equals("Low")) { //$NON-NLS-1$
return LOW;
}
if (priority.equals("Lowest")) { //$NON-NLS-1$
return LOWEST;
}
if (priority.equals("---")) { //$NON-NLS-1$
return NONE;
}
return null;
}
public PriorityLevel toPriorityLevel() {
switch (this) {
case HIGHEST:
return PriorityLevel.P1;
case HIGH:
return PriorityLevel.P2;
case NORMAL:
return PriorityLevel.P3;
case LOW:
return PriorityLevel.P4;
case LOWEST:
return PriorityLevel.P5;
case NONE:
return PriorityLevel.P3;
default:
return null;
}
}
@Override
public String toString() {
switch (this) {
case HIGHEST:
return "Highest"; //$NON-NLS-1$
case HIGH:
return "High"; //$NON-NLS-1$
case NORMAL:
return "Normal"; //$NON-NLS-1$
case LOW:
return "Low"; //$NON-NLS-1$
case LOWEST:
return "Lowest"; //$NON-NLS-1$
case NONE:
return "---"; //$NON-NLS-1$
default:
return null;
}
}
}
@Override
public boolean canGetTaskHistory(TaskRepository repository, ITask task) {
return Boolean.parseBoolean(repository.getProperty(IBugzillaConstants.BUGZILLA_USE_XMLRPC));
}
@Override
public TaskHistory getTaskHistory(TaskRepository repository, ITask task, IProgressMonitor monitor)
throws CoreException {
BugzillaClient client = getClientManager().getClient(repository, monitor);
BugHistory bugHistory = client.getBugHistory(task.getTaskId(), monitor).get(0);
TaskHistory history = new TaskHistory(repository, task);
for (BugHistory.Revision bugRevision : bugHistory.getRevisions()) {
TaskRevision revision = new TaskRevision(Long.toString(bugRevision.getWhen().getTime()),
bugRevision.getWhen(), repository.createPerson(bugRevision.getWho()));
history.add(revision);
for (BugHistory.Change bugChange : bugRevision.getChanges()) {
String attributeId;
if (bugChange.getAttachmentId() > 0) {
attributeId = TaskAttribute.PREFIX_ATTACHMENT + bugChange.getAttachmentId();
} else {
attributeId = bugChange.getFieldName();
}
TaskRevision.Change change = new TaskRevision.Change(attributeId, bugChange.getFieldName(),
bugChange.getRemoved(), bugChange.getAdded());
revision.add(change);
}
}
return history;
}
public static PriorityLevel getTaskPriority(String bugzillaPriority) {
if (bugzillaPriority != null) {
BugzillaPriorityLevel priority = BugzillaPriorityLevel.fromPriority(bugzillaPriority);
if (priority != null) {
return priority.toPriorityLevel();
}
}
return PriorityLevel.fromString(bugzillaPriority);
}
public static PriorityLevel getTaskPriority(String priority, List<String> priorities) {
if (priority != null && priorities != null && priorities.size() > 0) {
int size = priorities.size() - 1;
int index = 0;
for (String priority2test : priorities) {
if (priority.equals(priority2test)) {
float relativeValue = (float) index / size;
int value = (int) (relativeValue * 5) + 1;
return PriorityLevel.fromLevel(value);
}
index++;
}
}
return getTaskPriority(priority);
}
}