blob: eca41a7e68b416dfded635d40b82fd9542a8cfb9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 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.tasks.core.data;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants;
import org.eclipse.mylyn.internal.tasks.core.data.TaskDataExternalizer.Xml11InputStream;
import org.eclipse.mylyn.tasks.core.IRepositoryManager;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.xml.sax.SAXException;
/**
* @author Steffen Pingel
*/
public class TaskDataStore {
private static final String FILE_NAME_INTERNAL = "data.xml"; //$NON-NLS-1$
private final TaskDataExternalizer externalizer;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public TaskDataStore(IRepositoryManager taskRepositoryManager) {
this(new TaskDataExternalizer(taskRepositoryManager));
}
TaskDataStore(TaskDataExternalizer externalizer) {
this.externalizer = externalizer;
}
public TaskDataState discardEdits(File file) throws CoreException {
TaskDataState state = readState(file);
if (state != null) {
state.setEditsData(null);
}
writeState(file, state);
return state;
}
public TaskDataState getTaskDataState(File file) throws CoreException {
return readState(file);
}
public void putEdits(File file, TaskData data) throws CoreException {
Assert.isNotNull(file);
Assert.isNotNull(data);
TaskDataState state = readState(file);
if (state == null) {
state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId());
}
state.setEditsData(data);
writeState(file, state);
}
public TaskDataState putTaskData(File file, TaskData data, boolean setLastRead, boolean user) throws CoreException {
Assert.isNotNull(file);
Assert.isNotNull(data);
TaskDataState state = null;
try {
state = readState(file);
} catch (CoreException e) {
if (!user) {
throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN,
"Reading of existing task data failed. Forcing synchronization will override outgoing changes.", //$NON-NLS-1$
e));
}
}
if (state == null) {
state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId());
}
if (setLastRead) {
state.setLastReadData(state.getRepositoryData());
}
state.setRepositoryData(data);
writeState(file, state);
return state;
}
public TaskDataState setTaskData(File file, TaskData data) throws CoreException {
Assert.isNotNull(file);
Assert.isNotNull(data);
// TODO consider reading old task data and compare submitted results to check if all outgoing changes were accepted by repository
TaskDataState state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId());
state.setRepositoryData(data);
state.setEditsData(null);
state.setLastReadData(data);
writeState(file, state);
return state;
}
private TaskDataState readStateInternal(File file, boolean xml11) throws IOException, SAXException {
try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)))) {
in.getNextEntry();
// bug 268456: When TaskData that contains C0 control characters is written to disk using XML 1.0 reading it back
// in fails with a SAXException. The XML 1.1 standard allows C0 entities but fails if C1 entities. If C0 control
// characters are detected while parsing file as XML 1.0 a second attempt is made using XML 1.1. If the file contains
// C0 and C1 control characters reading will fail regardless.
if (xml11) {
return externalizer.readState(new Xml11InputStream(in));
} else {
return externalizer.readState(in);
}
}
}
private TaskDataState readState(File file) throws CoreException {
lock.readLock().lock();
try {
if (file.exists()) {
try {
try {
return readStateInternal(file, false);
} catch (SAXException e) {
// bug 268456: if reading fails, try again using a different XML version
if (e.getMessage() != null && (e.getMessage().contains("invalid XML character") //$NON-NLS-1$
|| e.getMessage().contains(" \"&#"))) { //$NON-NLS-1$
return readStateInternal(file, true);
} else {
throw e;
}
}
} catch (SAXException e) {
throw new IOException("Error parsing task data: " + e.getMessage(), e); //$NON-NLS-1$
}
}
return null;
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error reading task data", //$NON-NLS-1$
e));
} finally {
lock.readLock().unlock();
}
}
private void writeState(File file, TaskDataState state) throws CoreException {
lock.writeLock().lock();
try {
try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
out.setMethod(ZipOutputStream.DEFLATED);
ZipEntry entry = new ZipEntry(FILE_NAME_INTERNAL);
out.putNextEntry(entry);
externalizer.writeState(out, state);
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error writing task data", //$NON-NLS-1$
e));
} finally {
lock.writeLock().unlock();
}
}
public void putTaskData(File file, TaskDataState state) throws CoreException {
writeState(file, state);
}
public boolean deleteTaskData(File file) {
lock.writeLock().lock();
try {
return file.delete();
} finally {
lock.writeLock().unlock();
}
}
}