/******************************************************************************* * Copyright (c) 2004, 2014 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.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.", e)); //$NON-NLS-1$ } 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) { ObjectOutputStream out = null; try { Set<RepositoryConfiguration> tempConfigs; synchronized (repositoryConfigurations) { tempConfigs = new HashSet<RepositoryConfiguration>(repositoryConfigurations.values()); } if (tempConfigs.size() > 0) { out = new ObjectOutputStream(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$ } finally { if (out != null) { try { out.close(); } catch (IOException e) { // ignore } } } } } 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); } }