/*******************************************************************************
* 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$
}
}
}
}
}