/******************************************************************************* * Copyright (c) 2004, 2009 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 * Ken Sueda - XML serialization support *******************************************************************************/ package org.eclipse.mylyn.internal.tasks.core.externalization; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.internal.tasks.core.AbstractTask; import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer; import org.eclipse.mylyn.internal.tasks.core.DayDateRange; import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; import org.eclipse.mylyn.internal.tasks.core.ITransferList; import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; import org.eclipse.mylyn.internal.tasks.core.LocalTask; import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; import org.eclipse.mylyn.internal.tasks.core.RepositoryTaskHandleUtil; import org.eclipse.mylyn.internal.tasks.core.TaskActivityUtil; import org.eclipse.mylyn.internal.tasks.core.TaskCategory; import org.eclipse.mylyn.internal.tasks.core.TaskExternalizationException; import org.eclipse.mylyn.internal.tasks.core.TaskTask; import org.eclipse.mylyn.internal.tasks.core.UncategorizedTaskContainer; import org.eclipse.mylyn.internal.tasks.core.WeekDateRange; import org.eclipse.mylyn.tasks.core.AbstractTaskListMigrator; import org.eclipse.mylyn.tasks.core.IAttributeContainer; import org.eclipse.mylyn.tasks.core.IRepositoryManager; 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.ITask.SynchronizationState; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Subclass externalizers must override the get*TagName() methods for the types of externalized items they support to * ensure that their externalizer does not externalize tasks from other connectors incorrectly. These tag names uniquely * identify the externalizer to be used to read the task from externalized form on disk. The canCreateElementFor methods * specify which tasks the externalizer should write to disk. The TaskList is read on startup, so externalizers * extending this should not perform any slow (i.e., network) operations when overriding methods. * * @author Mik Kersten * @author Steffen Pingel * @author Mike Wu */ public final class DelegatingTaskExternalizer { static final String DEFAULT_PRIORITY = PriorityLevel.P3.toString(); static final String OUT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.S Z"; //$NON-NLS-1$ static final String IN_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.S z"; //$NON-NLS-1$ static final String KEY_NOTIFIED_INCOMING = "NotifiedIncoming"; //$NON-NLS-1$ public static final String KEY_NAME = "Name"; //$NON-NLS-1$ public static final String KEY_LABEL = "Label"; //$NON-NLS-1$ public static final String KEY_QUERY = "Query"; //$NON-NLS-1$ public static final String KEY_QUERY_STRING = "QueryString"; //$NON-NLS-1$ static final String KEY_HANDLE = "Handle"; //$NON-NLS-1$ public static final String KEY_REPOSITORY_URL = "RepositoryUrl"; //$NON-NLS-1$ public static final String KEY_CATEGORY = "Category"; //$NON-NLS-1$ static final String VAL_ROOT = "Root"; //$NON-NLS-1$ static final String KEY_SUBTASK = "SubTask"; //$NON-NLS-1$ static final String KEY_KIND = "Kind"; //$NON-NLS-1$ static final String KEY_TASK_CATEGORY = "Task" + KEY_CATEGORY; //$NON-NLS-1$ static final String KEY_LINK = "Link"; //$NON-NLS-1$ static final String KEY_PLAN = "Plan"; //$NON-NLS-1$ static final String KEY_TIME_ESTIMATED = "Estimated"; //$NON-NLS-1$ static final String KEY_ISSUEURL = "IssueURL"; //$NON-NLS-1$ static final String KEY_NOTES = "Notes"; //$NON-NLS-1$ static final String KEY_ACTIVE = "Active"; //$NON-NLS-1$ static final String KEY_PRIORITY = "Priority"; //$NON-NLS-1$ static final String KEY_PATH = "Path"; //$NON-NLS-1$ static final String VAL_FALSE = "false"; //$NON-NLS-1$ static final String VAL_TRUE = "true"; //$NON-NLS-1$ static final String KEY_DATE_END = "EndDate"; //$NON-NLS-1$ static final String KEY_QUERY_HIT = "QueryHit"; //$NON-NLS-1$ static final String KEY_TASK_REFERENCE = "TaskReference"; //$NON-NLS-1$ static final String KEY_DATE_CREATION = "CreationDate"; //$NON-NLS-1$ static final String KEY_DATE_REMINDER = "ReminderDate"; //$NON-NLS-1$ static final String KEY_DATE_SCHEDULED_START = "ScheduledStartDate"; //$NON-NLS-1$ static final String KEY_DATE_SCHEDULED_END = "ScheduledEndDate"; //$NON-NLS-1$ static final String KEY_DATE_MODIFICATION = "ModificationDate"; //$NON-NLS-1$ static final String KEY_DATE_DUE = "DueDate"; //$NON-NLS-1$ static final String KEY_REMINDED = "Reminded"; //$NON-NLS-1$ static final String KEY_FLOATING = "Floating"; //$NON-NLS-1$ /** * This element holds the date stamp recorded upon last transition to a synchronized state. */ static final String KEY_LAST_MOD_DATE = "LastModified"; //$NON-NLS-1$ static final String KEY_DIRTY = "Dirty"; //$NON-NLS-1$ static final String KEY_SYNC_STATE = "offlineSyncState"; //$NON-NLS-1$ static final String KEY_OWNER = "Owner"; //$NON-NLS-1$ static final String KEY_MARK_READ_PENDING = "MarkReadPending"; //$NON-NLS-1$ static final String KEY_STALE = "Stale"; //$NON-NLS-1$ static final String KEY_CONNECTOR_KIND = "ConnectorKind"; //$NON-NLS-1$ static final String KEY_TASK_ID = "TaskId"; //$NON-NLS-1$ public static final String KEY_LAST_REFRESH = "LastRefreshTimeStamp"; //$NON-NLS-1$ static final String NODE_ATTRIBUTE = "Attribute"; //$NON-NLS-1$ static final String NODE_QUERY = "Query"; //$NON-NLS-1$ static final String NODE_TASK = "Task"; //$NON-NLS-1$ static final String KEY_KEY = "Key"; //$NON-NLS-1$ // 2.0 -> 3.0 migration holds tasks to category handles private final Map parentCategoryMap; private final RepositoryModel repositoryModel; private List migrators; private boolean taskActivated; private final IRepositoryManager repositoryManager; private final List errors; public DelegatingTaskExternalizer(RepositoryModel repositoryModel, IRepositoryManager repositoryManager) { Assert.isNotNull(repositoryModel); Assert.isNotNull(repositoryManager); this.repositoryModel = repositoryModel; this.repositoryManager = repositoryManager; this.parentCategoryMap = new HashMap(); this.errors = new ArrayList(); this.migrators = Collections.emptyList(); } public void initialize(List migrators) { Assert.isNotNull(migrators); this.migrators = migrators; } public Element createCategoryElement(AbstractTaskCategory category, Document doc, Element parent) { Element node = doc.createElement(getCategoryTagName()); node.setAttribute(DelegatingTaskExternalizer.KEY_HANDLE, category.getHandleIdentifier()); node.setAttribute(DelegatingTaskExternalizer.KEY_NAME, category.getSummary()); parent.appendChild(node); for (ITask task : category.getChildren()) { createTaskReference(KEY_TASK_REFERENCE, task, doc, node); } return node; } @SuppressWarnings("deprecation") public Element createTaskElement(final AbstractTask task, Document doc, Element parent) { final Element node; if (task.getClass() == TaskTask.class || task instanceof LocalTask) { node = doc.createElement(NODE_TASK); } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "No externalizer for task: " + task)); //$NON-NLS-1$ return null; } node.setAttribute(KEY_CONNECTOR_KIND, task.getConnectorKind()); node.setAttribute(KEY_REPOSITORY_URL, task.getRepositoryUrl()); node.setAttribute(KEY_TASK_ID, task.getTaskId()); if (task.getTaskKey() != null) { node.setAttribute(KEY_KEY, task.getTaskKey()); } node.setAttribute(KEY_HANDLE, task.getHandleIdentifier()); node.setAttribute(KEY_LABEL, stripControlCharacters(task.getSummary())); node.setAttribute(KEY_PRIORITY, task.getPriority()); node.setAttribute(KEY_KIND, task.getTaskKind()); if (task.isActive()) { node.setAttribute(KEY_ACTIVE, VAL_TRUE); } else { node.setAttribute(KEY_ACTIVE, VAL_FALSE); } if (task.getUrl() != null) { node.setAttribute(KEY_ISSUEURL, task.getUrl()); } node.setAttribute(KEY_NOTES, stripControlCharacters(task.getNotes())); node.setAttribute(KEY_TIME_ESTIMATED, "" + task.getEstimatedTimeHours()); //$NON-NLS-1$ node.setAttribute(KEY_DATE_END, formatExternDate(task.getCompletionDate())); node.setAttribute(KEY_DATE_CREATION, formatExternDate(task.getCreationDate())); node.setAttribute(KEY_DATE_MODIFICATION, formatExternDate(task.getModificationDate())); node.setAttribute(KEY_DATE_DUE, formatExternDate(task.getDueDate())); if (task.getScheduledForDate() != null) { node.setAttribute(KEY_DATE_SCHEDULED_START, formatExternCalendar(task.getScheduledForDate().getStartDate())); node.setAttribute(KEY_DATE_SCHEDULED_END, formatExternCalendar(task.getScheduledForDate().getEndDate())); } if (task.isReminded()) { node.setAttribute(KEY_REMINDED, VAL_TRUE); } else { node.setAttribute(KEY_REMINDED, VAL_FALSE); } if (task.isStale()) { node.setAttribute(KEY_STALE, VAL_TRUE); } else { node.setAttribute(KEY_STALE, VAL_FALSE); } if (task.isMarkReadPending()) { node.setAttribute(KEY_MARK_READ_PENDING, VAL_TRUE); } else { node.setAttribute(KEY_MARK_READ_PENDING, VAL_FALSE); } if (task.getLastReadTimeStamp() != null) { node.setAttribute(KEY_LAST_MOD_DATE, task.getLastReadTimeStamp()); } if (task.isNotified()) { node.setAttribute(KEY_NOTIFIED_INCOMING, VAL_TRUE); } else { node.setAttribute(KEY_NOTIFIED_INCOMING, VAL_FALSE); } if (task.getSynchronizationState() != null) { node.setAttribute(KEY_SYNC_STATE, task.getSynchronizationState().name()); } else { node.setAttribute(KEY_SYNC_STATE, SynchronizationState.SYNCHRONIZED.name()); } if (task.getOwner() != null) { node.setAttribute(KEY_OWNER, task.getOwner()); } createAttributes(task, doc, node); for (ITask t : task.getChildren()) { createTaskReference(KEY_SUBTASK, t, doc, node); } parent.appendChild(node); return node; } private void createAttributes(IAttributeContainer container, Document doc, Element parent) { Map attributes = container.getAttributes(); for (Map.Entry entry : attributes.entrySet()) { Element node = doc.createElement(NODE_ATTRIBUTE); node.setAttribute(KEY_KEY, entry.getKey()); node.setTextContent(entry.getValue()); parent.appendChild(node); } } /** * creates nested task reference nodes named nodeName which include a handle to the task * * @return */ public Element createTaskReference(String nodeName, ITask task, Document doc, Element parent) { Element node = doc.createElement(nodeName); node.setAttribute(KEY_HANDLE, task.getHandleIdentifier()); parent.appendChild(node); return node; } /** * create tasks from the nodes provided and places them within the given container */ public void readTaskReferences(AbstractTaskContainer task, NodeList nodes, ITransferList tasklist) { for (int j = 0; j < nodes.getLength(); j++) { Node child = nodes.item(j); Element element = (Element) child; if (element.hasAttribute(KEY_HANDLE)) { String handle = element.getAttribute(KEY_HANDLE); AbstractTask subTask = tasklist.getTask(handle); if (subTask != null) { tasklist.addTask(subTask, task); } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Failed to add subtask with handle \"" + handle + "\" to \"" + task + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } } } @SuppressWarnings({ "restriction" }) private String stripControlCharacters(String text) { if (text == null) { return ""; //$NON-NLS-1$ } return org.eclipse.mylyn.internal.commons.core.XmlStringConverter.cleanXmlString(text); } private String formatExternDate(Date date) { if (date == null) { return ""; //$NON-NLS-1$ } SimpleDateFormat format = new SimpleDateFormat(OUT_DATE_FORMAT, Locale.ENGLISH); return format.format(date); } private String formatExternCalendar(Calendar date) { if (date == null) { return ""; //$NON-NLS-1$ } SimpleDateFormat format = new SimpleDateFormat(OUT_DATE_FORMAT, Locale.ENGLISH); return format.format(date.getTime()); } public void readCategory(Node node, ITransferList taskList) { Element element = (Element) node; AbstractTaskCategory category = null; if (element.hasAttribute(KEY_NAME)) { String name = element.getAttribute(KEY_NAME); String handle = name; if (element.hasAttribute(KEY_HANDLE)) { handle = element.getAttribute(KEY_HANDLE); } category = taskList.getContainerForHandle(handle); if (category == null) { category = new TaskCategory(handle, name); taskList.addCategory((TaskCategory) category); } else if (!UncategorizedTaskContainer.HANDLE.equals(handle)) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Category with handle \"" + name //$NON-NLS-1$ + "\" already exists in task list")); //$NON-NLS-1$ } } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Category is missing name attribute")); //$NON-NLS-1$ // LEGACY: registry categories did not have names // category = taskList.getArchiveContainer(); // a null category will now go into appropriate orphaned category } NodeList list = node.getChildNodes(); readTaskReferences(category, list, taskList); } @SuppressWarnings("deprecation") public final AbstractTask readTask(Node node, AbstractTaskCategory legacyCategory, ITask parent) throws CoreException { String handle; String taskId; String repositoryUrl; String summary = ""; //$NON-NLS-1$ final Element element = (Element) node; if (element.hasAttribute(KEY_REPOSITORY_URL) && element.hasAttribute(KEY_TASK_ID) && element.hasAttribute(KEY_HANDLE)) { handle = element.getAttribute(KEY_HANDLE); repositoryUrl = element.getAttribute(KEY_REPOSITORY_URL); taskId = element.getAttribute(KEY_TASK_ID); } else if (element.hasAttribute(KEY_HANDLE)) { handle = element.getAttribute(KEY_HANDLE); repositoryUrl = RepositoryTaskHandleUtil.getRepositoryUrl(handle); taskId = RepositoryTaskHandleUtil.getTaskId(handle); } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Task is missing handle attribute")); //$NON-NLS-1$ return null; } if (element.hasAttribute(KEY_LABEL)) { summary = element.getAttribute(KEY_LABEL); } AbstractTask task = null; AbstractTaskListMigrator taskMigrator = null; if (NODE_TASK.equals(node.getNodeName())) { String connectorKind = element.getAttribute(DelegatingTaskExternalizer.KEY_CONNECTOR_KIND); task = readDefaultTask(connectorKind, repositoryUrl, taskId, summary, element); } // attempt migration from < 3.0 task list if (task == null) { for (AbstractTaskListMigrator migrator : migrators) { if (node.getNodeName().equals(migrator.getTaskElementName())) { task = readDefaultTask(migrator.getConnectorKind(), repositoryUrl, taskId, summary, element); taskMigrator = migrator; break; } } } // populate common attributes if (task != null) { if (repositoryManager.getRepositoryConnector(task.getConnectorKind()) == null) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for task with kind \"" + task.getConnectorKind() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ return null; } readTaskInfo(task, element, parent, legacyCategory); readAttributes(task, element); if (taskMigrator != null) { if (task.getSynchronizationState() == SynchronizationState.INCOMING && task.getLastReadTimeStamp() == null) { task.setSynchronizationState(SynchronizationState.INCOMING_NEW); } task.setTaskKey(task.getTaskId()); final AbstractTaskListMigrator finalTaskMigrator = taskMigrator; final AbstractTask finalTask = task; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Task migration failed for task \"" + finalTask + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ } public void run() throws Exception { finalTaskMigrator.migrateTask(finalTask, element); } }); } return task; } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for task node \"" //$NON-NLS-1$ + node.getNodeName() + "\"")); //$NON-NLS-1$ return null; } } private void readAttributes(IAttributeContainer container, Element parent) { NodeList list = parent.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { Node child = list.item(i); if (child instanceof Element && child.getNodeName().equals(NODE_ATTRIBUTE)) { Element element = (Element) child; String key = element.getAttribute(KEY_KEY); if (key.length() > 0) { container.setAttribute(key, element.getTextContent()); } } } } @SuppressWarnings("deprecation") private void readTaskInfo(AbstractTask task, Element element, ITask parent, AbstractTaskCategory legacyCategory) { if (element.hasAttribute(KEY_CATEGORY)) { // Migration 2.0 -> 3.0 task list. Category no longer maintained on the task element but // task handles held within category nodes similar to query children String categoryHandle = element.getAttribute(KEY_CATEGORY); if (categoryHandle.equals(VAL_ROOT)) { categoryHandle = UncategorizedTaskContainer.HANDLE; } //task.setCategoryHandle(categoryHandle); parentCategoryMap.put(task, categoryHandle); } if (element.hasAttribute(KEY_PRIORITY)) { task.setPriority(element.getAttribute(KEY_PRIORITY)); } else { task.setPriority(DEFAULT_PRIORITY); } if (element.hasAttribute(KEY_KIND)) { task.setTaskKind(element.getAttribute(KEY_KIND)); } if (!taskActivated && element.getAttribute(KEY_ACTIVE).compareTo(VAL_TRUE) == 0) { task.setActive(true); taskActivated = true; } else { task.setActive(false); } if (element.hasAttribute(KEY_ISSUEURL)) { task.setUrl(element.getAttribute(KEY_ISSUEURL)); } else { task.setUrl(""); //$NON-NLS-1$ } if (element.hasAttribute(KEY_NOTES)) { task.setNotes(element.getAttribute(KEY_NOTES)); } else { task.setNotes(""); //$NON-NLS-1$ } if (element.hasAttribute(KEY_TIME_ESTIMATED)) { String est = element.getAttribute(KEY_TIME_ESTIMATED); try { int estimate = Integer.parseInt(est); task.setEstimatedTimeHours(estimate); } catch (Exception e) { task.setEstimatedTimeHours(0); } } else { task.setEstimatedTimeHours(0); } if (element.hasAttribute(KEY_DATE_END)) { task.setCompletionDate(getDateFromString(element.getAttribute(KEY_DATE_END))); } else { task.setCompletionDate(null); } if (element.hasAttribute(KEY_DATE_CREATION)) { task.setCreationDate(getDateFromString(element.getAttribute(KEY_DATE_CREATION))); } else { task.setCreationDate(null); } if (element.hasAttribute(KEY_DATE_MODIFICATION)) { task.setModificationDate(getDateFromString(element.getAttribute(KEY_DATE_MODIFICATION))); } else { task.setModificationDate(null); } if (element.hasAttribute(KEY_DATE_DUE)) { task.setDueDate(getDateFromString(element.getAttribute(KEY_DATE_DUE))); } else { task.setDueDate(null); } // Legacy 2.3.2 -> 3.0 migration of scheduled date boolean isFloating = false; if (element.hasAttribute(KEY_FLOATING) && element.getAttribute(KEY_FLOATING).compareTo(VAL_TRUE) == 0) { isFloating = true; } else { isFloating = false; } if (element.hasAttribute(KEY_DATE_REMINDER)) { Date date = getDateFromString(element.getAttribute(KEY_DATE_REMINDER)); if (date != null) { if (isFloating) { task.setScheduledForDate(TaskActivityUtil.getWeekOf(date)); } else { task.setScheduledForDate(TaskActivityUtil.getDayOf(date)); } } } // Scheduled date range (3.0) if (element.hasAttribute(KEY_DATE_SCHEDULED_START) && element.hasAttribute(KEY_DATE_SCHEDULED_END)) { Date startDate = getDateFromString(element.getAttribute(KEY_DATE_SCHEDULED_START)); Date endDate = getDateFromString(element.getAttribute(KEY_DATE_SCHEDULED_END)); if (startDate != null && endDate != null && startDate.compareTo(endDate) <= 0) { Calendar calStart = TaskActivityUtil.getCalendar(); calStart.setTime(startDate); Calendar calEnd = TaskActivityUtil.getCalendar(); calEnd.setTime(endDate); if (DayDateRange.isDayRange(calStart, calEnd)) { task.setScheduledForDate(new DayDateRange(calStart, calEnd)); } else if (WeekDateRange.isWeekRange(calStart, calEnd)) { task.setScheduledForDate(new WeekDateRange(calStart, calEnd)); } else { // Neither week nor day found, default to today task.setScheduledForDate(TaskActivityUtil.getDayOf(new Date())); } } } if (element.hasAttribute(KEY_REMINDED) && element.getAttribute(KEY_REMINDED).compareTo(VAL_TRUE) == 0) { task.setReminded(true); } else { task.setReminded(false); } if (element.hasAttribute(KEY_STALE) && element.getAttribute(KEY_STALE).compareTo(VAL_TRUE) == 0) { task.setStale(true); } else { task.setStale(false); } if (element.hasAttribute(KEY_MARK_READ_PENDING) && element.getAttribute(KEY_MARK_READ_PENDING).compareTo(VAL_TRUE) == 0) { task.setMarkReadPending(true); } else { task.setMarkReadPending(false); } task.setSynchronizing(false); if (element.hasAttribute(KEY_REPOSITORY_URL)) { task.setRepositoryUrl(element.getAttribute(KEY_REPOSITORY_URL)); } if (element.hasAttribute(KEY_LAST_MOD_DATE) && !element.getAttribute(KEY_LAST_MOD_DATE).equals("")) { //$NON-NLS-1$ task.setLastReadTimeStamp(element.getAttribute(KEY_LAST_MOD_DATE)); } if (element.hasAttribute(KEY_OWNER)) { task.setOwner(element.getAttribute(KEY_OWNER)); } if (VAL_TRUE.equals(element.getAttribute(KEY_NOTIFIED_INCOMING))) { task.setNotified(true); } else { task.setNotified(false); } if (element.hasAttribute(KEY_SYNC_STATE)) { try { SynchronizationState state = SynchronizationState.valueOf(element.getAttribute(KEY_SYNC_STATE)); task.setSynchronizationState(state); } catch (IllegalArgumentException e) { // invalid sync state, ignore // TODO log this to a multi-status } } if (element.hasAttribute(KEY_KEY)) { task.setTaskKey(element.getAttribute(KEY_KEY)); } else { task.setTaskKey(null); } } private Date getDateFromString(String dateString) { Date date = null; if ("".equals(dateString)) { //$NON-NLS-1$ return null; } SimpleDateFormat format = new SimpleDateFormat(IN_DATE_FORMAT, Locale.ENGLISH); try { date = format.parse(dateString); } catch (ParseException e) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Could not parse date \"" //$NON-NLS-1$ + dateString + "\"", e)); //$NON-NLS-1$ } return date; } private String getCategoryTagName() { return KEY_TASK_CATEGORY; } public Element createQueryElement(final RepositoryQuery query, Document doc, Element parent) { final Element node; if (query.getClass() == RepositoryQuery.class) { node = doc.createElement(NODE_QUERY); } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing factory to externalize query \"" + query + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ return null; } node.setAttribute(KEY_HANDLE, query.getHandleIdentifier()); node.setAttribute(KEY_CONNECTOR_KIND, query.getConnectorKind()); node.setAttribute(KEY_NAME, query.getSummary()); node.setAttribute(KEY_QUERY_STRING, query.getUrl()); node.setAttribute(KEY_REPOSITORY_URL, query.getRepositoryUrl()); if (query.getLastSynchronizedTimeStamp() != null) { node.setAttribute(KEY_LAST_REFRESH, query.getLastSynchronizedTimeStamp()); } createAttributes(query, doc, node); for (ITask hit : query.getChildren()) { createTaskReference(KEY_QUERY_HIT, hit, doc, node); } parent.appendChild(node); return node; } public Map getLegacyParentCategoryMap() { return parentCategoryMap; } /** * Reads the Query from the specified Node. If taskList is not null, then also adds this query to the TaskList * * @throws TaskExternalizationException */ public RepositoryQuery readQuery(Node node) { final Element element = (Element) node; String repositoryUrl = element.getAttribute(DelegatingTaskExternalizer.KEY_REPOSITORY_URL); String queryString = element.getAttribute(KEY_QUERY_STRING); if (queryString.length() == 0) { // fall back for legacy queryString = element.getAttribute(KEY_QUERY); } String label = element.getAttribute(DelegatingTaskExternalizer.KEY_NAME); if (label.length() == 0) { // fall back for legacy label = element.getAttribute(DelegatingTaskExternalizer.KEY_LABEL); } AbstractTaskListMigrator queryMigrator = null; RepositoryQuery query = null; if (NODE_QUERY.equals(node.getNodeName())) { String connectorKind = element.getAttribute(DelegatingTaskExternalizer.KEY_CONNECTOR_KIND); query = readDefaultQuery(connectorKind, repositoryUrl, queryString, label, element); } // attempt migration from < 3.0 task list if (query == null) { for (AbstractTaskListMigrator migrator : migrators) { Set queryTagNames = migrator.getQueryElementNames(); if (queryTagNames != null && queryTagNames.contains(node.getNodeName())) { query = readDefaultQuery(migrator.getConnectorKind(), repositoryUrl, queryString, label, element); queryMigrator = migrator; break; } } } // populate common attributes if (query != null) { if (repositoryManager.getRepositoryConnector(query.getConnectorKind()) == null) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for query with kind \"" + query.getConnectorKind() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ return null; } if (element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH) != null && !element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH).equals("")) { //$NON-NLS-1$ query.setLastSynchronizedStamp(element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH)); } String handle = element.getAttribute(DelegatingTaskExternalizer.KEY_HANDLE); if (handle.length() > 0) { query.setHandleIdentifier(handle); } readAttributes(query, element); if (queryMigrator != null) { query.setHandleIdentifier(label); final AbstractTaskListMigrator finalQueryMigrator = queryMigrator; final RepositoryQuery finalQuery = query; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Query migration failed for query \"" + finalQuery + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ } public void run() throws Exception { finalQueryMigrator.migrateQuery(finalQuery, element); } }); } return query; } else { errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for query node \"" //$NON-NLS-1$ + node.getNodeName() + "\"")); //$NON-NLS-1$ return null; } } private RepositoryQuery readDefaultQuery(String connectorKind, String repositoryUrl, String queryString, String label, Element childElement) { TaskRepository taskRepository = repositoryModel.getTaskRepository(connectorKind, repositoryUrl); IRepositoryQuery query = repositoryModel.createRepositoryQuery(taskRepository); query.setSummary(label); query.setUrl(queryString); return (RepositoryQuery) query; } private AbstractTask readDefaultTask(String connectorKind, String repositoryUrl, String taskId, String summary, Element element) { TaskRepository taskRepository = repositoryModel.getTaskRepository(connectorKind, repositoryUrl); if (repositoryUrl.equals(LocalRepositoryConnector.REPOSITORY_URL)) { LocalTask task = new LocalTask(taskId, summary); return task; } ITask task = repositoryModel.createTask(taskRepository, taskId); task.setSummary(summary); return (AbstractTask) task; } public void reset() { parentCategoryMap.clear(); errors.clear(); } public void clearErrorStatus() { errors.clear(); } public Status getErrorStatus() { if (errors.size() > 0) { return new MultiStatus(ITasksCoreConstants.ID_PLUGIN, 0, errors.toArray(new IStatus[0]), "Problems encounted while externalizing task list", null); //$NON-NLS-1$ } return null; } }