/******************************************************************************* * Copyright (c) 2004, 2010 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 - improvements * Jevgeni Holodkov - improvements *******************************************************************************/ package org.eclipse.mylyn.internal.tasks.core.externalization; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.internal.tasks.core.AbstractTask; import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; import org.eclipse.mylyn.internal.tasks.core.ITransferList; import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; import org.eclipse.mylyn.tasks.core.AbstractTaskListMigrator; import org.eclipse.mylyn.tasks.core.IRepositoryManager; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * @author Mik Kersten * @author Rob Elves */ public class TaskListExternalizer { private static final String ERROR_TASKLIST_READ = "Failed to load Task List"; //$NON-NLS-1$ private static final String TRANSFORM_PROPERTY_VERSION = "version"; //$NON-NLS-1$ // May 2007: There was a bug when reading in 1.1 // Result was an infinite loop within the parser private static final String XML_VERSION = "1.0"; //$NON-NLS-1$ public static final String ATTRIBUTE_VERSION = "Version"; //$NON-NLS-1$ public static final String ELEMENT_TASK_LIST = "TaskList"; //$NON-NLS-1$ // Mylyn 3.0 private static final String VALUE_VERSION = "2.0"; //$NON-NLS-1$ // Mylyn 2.3.2 //private static final String VALUE_VERSION_1_0_1 = "1.0.1"; private static final String VALUE_VERSION_1_0_0 = "1.0.0"; //$NON-NLS-1$ private final DelegatingTaskExternalizer delegatingExternalizer; private final List<Node> orphanedNodes = new ArrayList<Node>(); private Document orphanedDocument; private String readVersion = ""; //$NON-NLS-1$ public TaskListExternalizer(RepositoryModel repositoryModel, IRepositoryManager repositoryManager) { this.delegatingExternalizer = new DelegatingTaskExternalizer(repositoryModel, repositoryManager); try { this.orphanedDocument = createDocument(); } catch (CoreException e) { this.orphanedDocument = null; StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed to create document for orphaned nodes", e)); //$NON-NLS-1$ } } public void initialize(List<AbstractTaskListMigrator> migrators) { this.delegatingExternalizer.initialize(migrators); } public void writeTaskList(ITransferList taskList, File outFile) throws CoreException { try { FileOutputStream outStream = new FileOutputStream(outFile); try { Document doc = createTaskListDocument(taskList); ZipOutputStream zipOutStream = new ZipOutputStream(outStream); ZipEntry zipEntry = new ZipEntry(ITasksCoreConstants.OLD_TASK_LIST_FILE); zipOutStream.putNextEntry(zipEntry); zipOutStream.setMethod(ZipOutputStream.DEFLATED); writeDocument(doc, zipOutStream); zipOutStream.flush(); zipOutStream.closeEntry(); zipOutStream.finish(); } finally { outStream.close(); } } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Saving Task List failed", //$NON-NLS-1$ e)); } } private Document createTaskListDocument(ITransferList taskList) throws CoreException { Document doc = createDocument(); delegatingExternalizer.clearErrorStatus(); Element root = doc.createElement(ELEMENT_TASK_LIST); root.setAttribute(ATTRIBUTE_VERSION, VALUE_VERSION); doc.appendChild(root); // create task nodes... for (AbstractTask task : taskList.getAllTasks()) { delegatingExternalizer.createTaskElement(task, doc, root); } // create the category nodes... for (AbstractTaskCategory category : taskList.getCategories()) { delegatingExternalizer.createCategoryElement(category, doc, root); } // create query nodes... for (RepositoryQuery query : taskList.getQueries()) { delegatingExternalizer.createQueryElement(query, doc, root); } // Persist orphaned tasks... for (Node node : orphanedNodes) { Node tempNode = doc.importNode(node, true); if (tempNode != null) { root.appendChild(tempNode); } } if (delegatingExternalizer.getErrorStatus() != null) { StatusHandler.log(delegatingExternalizer.getErrorStatus()); } return doc; } private void writeDocument(Document doc, OutputStream outputStream) throws CoreException { Source source = new DOMSource(doc); Result result = new StreamResult(outputStream); try { Transformer xformer = TransformerFactory.newInstance().newTransformer(); xformer.setOutputProperty(TRANSFORM_PROPERTY_VERSION, XML_VERSION); xformer.transform(source, result); } catch (TransformerException e) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed write task list", //$NON-NLS-1$ e)); } } private Document createDocument() throws CoreException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; try { db = dbf.newDocumentBuilder(); return db.newDocument(); } catch (ParserConfigurationException e) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed to create document", e)); //$NON-NLS-1$ } } public void readTaskList(ITransferList taskList, File inFile) throws CoreException { if (!inFile.exists()) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task list file not found \"" + inFile.getAbsolutePath() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ } if (inFile.length() == 0) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task list file contains no data \"" + inFile.getAbsolutePath() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ } delegatingExternalizer.reset(); orphanedNodes.clear(); try { this.orphanedDocument = createDocument(); } catch (CoreException e) { this.orphanedDocument = null; StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed to create document for orphaned nodes", e)); //$NON-NLS-1$ } Document doc = openTaskList(inFile); Element root = doc.getDocumentElement(); readVersion = root.getAttribute(ATTRIBUTE_VERSION); if (readVersion.equals(VALUE_VERSION_1_0_0)) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task list version \"" //$NON-NLS-1$ + readVersion + "\" not supported")); //$NON-NLS-1$ } NodeList list = root.getChildNodes(); // read tasks Map<AbstractTask, NodeList> tasksWithSubtasks = new HashMap<AbstractTask, NodeList>(); for (int i = 0; i < list.getLength(); i++) { Node child = list.item(i); if (!child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_CATEGORY) && !child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_QUERY)) { AbstractTask task = delegatingExternalizer.readTask(child, null, null); if (task != null) { taskList.addTask(task); if (child.getChildNodes() != null && child.getChildNodes().getLength() > 0) { tasksWithSubtasks.put(task, child.getChildNodes()); } } else { addOrphan(child); } } } // create subtask hierarchy for (AbstractTask task : tasksWithSubtasks.keySet()) { NodeList nodes = tasksWithSubtasks.get(task); delegatingExternalizer.readTaskReferences(task, nodes, taskList); } // read queries for (int i = 0; i < list.getLength(); i++) { Node child = list.item(i); if (child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_QUERY)) { RepositoryQuery query = delegatingExternalizer.readQuery(child); if (query != null) { taskList.addQuery(query); if (child.getChildNodes() != null && child.getChildNodes().getLength() > 0) { delegatingExternalizer.readTaskReferences(query, child.getChildNodes(), taskList); } } else { addOrphan(child); } } } // Read Categories for (int i = 0; i < list.getLength(); i++) { Node child = list.item(i); if (child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_CATEGORY)) { delegatingExternalizer.readCategory(child, taskList); } } // Legacy migration for task nodes that have the old Category handle on the element Map<AbstractTask, String> legacyParentCategoryMap = delegatingExternalizer.getLegacyParentCategoryMap(); if (legacyParentCategoryMap.size() > 0) { for (AbstractTask task : legacyParentCategoryMap.keySet()) { AbstractTaskCategory category = taskList.getContainerForHandle(legacyParentCategoryMap.get(task)); if (category != null) { taskList.addTask(task, category); } } } // if (delegatingExternalizer.getErrorStatus() != null) { // StatusHandler.log(delegatingExternalizer.getErrorStatus()); // } } private void addOrphan(Node child) { // copy node to separate document to avoid retaining entire dom if (orphanedDocument != null) { orphanedNodes.add(orphanedDocument.importNode(child, true)); } else { orphanedNodes.add(child); } } /** * Opens the specified XML file and parses it into a DOM Document. Filename - the name of the file to open Return - * the Document built from the XML file Throws - XMLException if the file cannot be parsed as XML - IOException if * the file cannot be opened * * @throws CoreException */ private Document openTaskList(File inputFile) throws CoreException { InputStream in = null; try { if (inputFile.getName().endsWith(ITasksCoreConstants.FILE_EXTENSION)) { in = new ZipInputStream(new FileInputStream(inputFile)); ZipEntry entry = ((ZipInputStream) in).getNextEntry(); while (entry != null) { if (ITasksCoreConstants.OLD_TASK_LIST_FILE.equals(entry.getName())) { break; } entry = ((ZipInputStream) in).getNextEntry(); } if (entry == null) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task list file contains no entry for the task list")); //$NON-NLS-1$ } } else { in = new FileInputStream(inputFile); } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(in); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, ERROR_TASKLIST_READ, e)); } finally { if (in != null) { try { in.close(); } catch (IOException e) { StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed to close task list", e)); //$NON-NLS-1$ } } } } }