diff options
author | Sam Davis | 2016-07-29 23:31:42 +0000 |
---|---|---|
committer | Gerrit Code Review @ Eclipse.org | 2016-08-11 23:58:27 +0000 |
commit | 87d261761bc06e87322fdd61d94b264f451c4518 (patch) | |
tree | 2e4996abe8f90b75b4f416a73815e9e59150d8ef | |
parent | 47a134c4d308149cba23f62df67cb8e4b19c2db8 (diff) | |
download | org.eclipse.mylyn.tasks-87d261761bc06e87322fdd61d94b264f451c4518.tar.gz org.eclipse.mylyn.tasks-87d261761bc06e87322fdd61d94b264f451c4518.tar.xz org.eclipse.mylyn.tasks-87d261761bc06e87322fdd61d94b264f451c4518.zip |
498906: provide ability for users to migrate to new versions of
connectors
Change-Id: I6980b07ab7e984f4c7ea40fd8649093196d25755
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=498906
16 files changed, 2972 insertions, 1 deletions
diff --git a/org.eclipse.mylyn.tasks.ui.tests/META-INF/MANIFEST.MF b/org.eclipse.mylyn.tasks.ui.tests/META-INF/MANIFEST.MF index 376a98582..cc88e96b7 100644 --- a/org.eclipse.mylyn.tasks.ui.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.mylyn.tasks.ui.tests/META-INF/MANIFEST.MF @@ -12,7 +12,9 @@ Require-Bundle: org.junit;bundle-version="4.8.2", org.hamcrest;bundle-version="[1.0.0,2.0.0)", org.eclipse.equinox.security, org.eclipse.mylyn.tasks.core, - org.eclipse.mylyn.tasks.tests + org.eclipse.mylyn.tasks.tests, + org.eclipse.mylyn.tests.util Export-Package: org.eclipse.mylyn.internal.tasks.ui.editors;x-internal:=true, + org.eclipse.mylyn.internal.tasks.ui.migrator;x-internal:=true, org.eclipse.mylyn.tasks.ui.editors, org.eclipse.mylyn.tasks.ui.wizards diff --git a/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizardTest.java b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizardTest.java new file mode 100644 index 000000000..580224e31 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizardTest.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Tasktop EULA + * which accompanies this distribution, and is available at + * http://tasktop.com/legal + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.ui.migrator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.mylyn.commons.workbench.WorkbenchUtil; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.internal.tasks.ui.migrator.CompleteConnectorMigrationWizard.MapContentProvider; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskListView; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.ui.TaskElementLabelProvider; +import org.eclipse.mylyn.tests.util.TestFixture; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class CompleteConnectorMigrationWizardTest { + + private CompleteConnectorMigrationWizard wizard; + + private ConnectorMigrator migrator; + + private ConnectorMigrationUi migrationUi; + + @SuppressWarnings("unchecked") + @Before + public void setUp() { + DefaultTasksState tasksState = new DefaultTasksState(); + migrationUi = spy(new ConnectorMigrationUi(TaskListView.getFromActivePerspective(), + TasksUiPlugin.getBackupManager(), tasksState)); + doNothing().when(migrationUi).warnOfValidationFailure((List<TaskRepository>) any(List.class)); + doNothing().when(migrationUi).notifyMigrationComplete(); + migrator = spy(new ConnectorMigrator(ImmutableMap.of("mock", "mock.new"), "", tasksState, migrationUi)); + } + + @After + public void tearDown() throws Exception { + TestFixture.resetTaskList(); + } + + @Test + public void addPages() { + createWizard(new CompleteConnectorMigrationWizard(migrator)); + assertEquals(2, wizard.getPageCount()); + } + + @Test + public void firstPage() { + IWizardContainer container = createWizard(new CompleteConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + assertEquals("Have You Recreated Your Queries?", firstPage.getTitle()); + assertEquals( + "Migration will remove your old queries. Please ensure you have created the new queries you want. " + + "Your old and new queries are shown below and you can edit them by double-clicking.", + firstPage.getMessage()); + assertTrue(firstPage.getControl() instanceof Composite); + Composite control = (Composite) firstPage.getControl(); + assertEquals(4, control.getChildren().length); + assertTrue(control.getChildren()[0] instanceof Label); + assertTrue(control.getChildren()[1] instanceof Label); + assertTrue(control.getChildren()[2] instanceof Tree); + assertTrue(control.getChildren()[3] instanceof Tree); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void queryTreeShowsOnlySelectedConnectors() { + migrator = new ConnectorMigrator(ImmutableMap.of("mock", "mock.new", "kind", "kind.new"), "", + new DefaultTasksState(), migrationUi); + migrator.setConnectorsToMigrate(ImmutableList.of("kind")); + createWizard(new CompleteConnectorMigrationWizard(migrator)); + ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class); + verify(wizard, times(2)).createRepositoryQueryMap(captor.capture()); + assertEquals(ImmutableSet.of("kind"), ImmutableSet.copyOf(captor.getAllValues().get(0))); + assertEquals(ImmutableSet.of("kind.new"), ImmutableSet.copyOf(captor.getAllValues().get(1))); + + migrator = new ConnectorMigrator(ImmutableMap.of("mock", "mock.new", "kind", "kind.new"), "", + new DefaultTasksState(), migrationUi); + migrator.setConnectorsToMigrate(ImmutableList.of("mock", "kind")); + createWizard(new CompleteConnectorMigrationWizard(migrator)); + captor = ArgumentCaptor.forClass(Collection.class); + verify(wizard, times(2)).createRepositoryQueryMap(captor.capture()); + assertEquals(ImmutableSet.of("mock", "kind"), ImmutableSet.copyOf(captor.getAllValues().get(0))); + assertEquals(ImmutableSet.of("mock.new", "kind.new"), ImmutableSet.copyOf(captor.getAllValues().get(1))); + } + + @Test + public void secondPage() { + IWizardContainer container = createWizard(new CompleteConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + IWizardPage secondPage = firstPage.getNextPage(); + assertEquals("Complete Migration", secondPage.getTitle()); + assertEquals("Clicking finish will migrate your tasks and private data. This may take a while.", + secondPage.getMessage()); + assertTrue(secondPage.getControl() instanceof Composite); + Composite control = (Composite) secondPage.getControl(); + assertEquals(1, control.getChildren().length); + assertTrue(control.getChildren()[0] instanceof Text); + String text = ((Text) control.getChildren()[0]).getText(); + assertTrue(text.contains("When you click finish, your context, scheduled dates, private notes and other data " + + "will be migrated to the new connectors. Any tasks in your task list that are not included in the new " + + "queries you created will be downloaded using the new connectors. The old tasks, " + + "queries, and repositories will be deleted.")); + assertTrue(text.contains("This may take a while. You should not use the task list or task editor while this is happening. " + + "You will be prompted when migration is complete.")); + assertTrue(text.contains("You will be able to " + + "undo the migration by selecting \"Restore Tasks from History\" in the Task List view menu and choosing the " + + "connector-migration-*.zip file stored in <workspace>/.metadata/.mylyn/backup. This will restore your task " + + "list and repositories to the state they were in before the migration, but any data stored by 3rd party " + + "plugins for Mylyn may be lost")); + } + + @Test + public void performFinish() throws InvocationTargetException, InterruptedException, IOException { + createWizard(new CompleteConnectorMigrationWizard(migrator)); + assertTrue(wizard.performFinish()); + } + + @Test + public void isPageComplete() throws Exception { + IWizardContainer container = createWizard(new CompleteConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + IWizardPage secondPage = firstPage.getNextPage(); + assertTrue(firstPage.isPageComplete()); + assertFalse(secondPage.isPageComplete()); + + container.showPage(secondPage); + assertTrue(firstPage.isPageComplete()); + assertTrue(secondPage.isPageComplete()); + } + + @Test + public void createQueryTree() throws Exception { + TaskRepository repository = createRepository("mock", "http://mock"); + Map<TaskRepository, ? extends Set<RepositoryQuery>> queries = ImmutableMap.of(repository, + ImmutableSet.of(new RepositoryQuery("mock", "mock"))); + TreeViewer viewer = new CompleteConnectorMigrationWizard(migrator).createQueryTree(WorkbenchUtil.getShell(), + queries); + assertEquals(queries, viewer.getInput()); + assertTrue(viewer.getContentProvider() instanceof MapContentProvider); + assertTrue(viewer.getLabelProvider() instanceof TaskElementLabelProvider); + assertNotNull(((TaskElementLabelProvider) viewer.getLabelProvider()).getImage(repository)); + assertEquals("http://mock", ((TaskElementLabelProvider) viewer.getLabelProvider()).getText(repository)); + + assertEquals(0, viewer.getExpandedElements().length); + spinEventLoop(); + assertEquals(1, viewer.getExpandedElements().length); + } + + @Test + public void createRepositoryQueryMap() throws Exception { + TaskRepository repository1 = createRepository("mock", "http://mock"); + TaskRepository repository2 = createRepository("mock", "http://mock2"); + ImmutableSet<TaskRepository> repositories = ImmutableSet.of(repository1, repository2); + RepositoryQuery query1 = createQuery(repository1); + RepositoryQuery query2 = createQuery(repository1); + RepositoryQuery query3 = createQuery(repository2); + RepositoryQuery query4 = createQuery(repository2); + + Map<TaskRepository, Set<RepositoryQuery>> map = // + new CompleteConnectorMigrationWizard(migrator).createRepositoryQueryMap(ImmutableList.of("mock")); + assertEquals(repositories, map.keySet()); + assertEquals(ImmutableSet.of(query1, query2), map.get(repository1)); + assertEquals(ImmutableSet.of(query3, query4), map.get(repository2)); + } + + @Test + public void createRepositoryQueryMapExcludesRepositoryWithNoQueries() throws Exception { + TaskRepository repository = createRepository("mock", "http://mock"); + createRepository("mock", "http://mock2"); + RepositoryQuery query = createQuery(repository); + + Map<TaskRepository, Set<RepositoryQuery>> map = // + new CompleteConnectorMigrationWizard(migrator).createRepositoryQueryMap(ImmutableList.of("mock")); + assertEquals(ImmutableSet.of(repository), map.keySet()); + assertEquals(ImmutableSet.of(query), map.get(repository)); + } + + @Test + public void createRepositoryQueryMapMigratedQuery() throws Exception { + TaskRepository repository = createRepository("mock", "http://mock"); + TaskRepository migratedRepository = createRepository("mock-new", "http://mock"); + RepositoryQuery query = createQuery(repository); + RepositoryQuery migratedQuery = createQuery(migratedRepository); + + Map<TaskRepository, Set<RepositoryQuery>> map = // + new CompleteConnectorMigrationWizard(migrator).createRepositoryQueryMap(ImmutableList.of("mock")); + assertEquals(ImmutableSet.of(repository), map.keySet()); + assertEquals(ImmutableSet.of(query), map.get(repository)); + + map = new CompleteConnectorMigrationWizard(migrator).createRepositoryQueryMap(ImmutableList.of("mock-new")); + assertEquals(ImmutableSet.of(migratedRepository), map.keySet()); + assertEquals(ImmutableSet.of(migratedQuery), map.get(migratedRepository)); + } + + protected TaskRepository createRepository(String kind, String url) { + TaskRepository repository = new TaskRepository(kind, url); + migrator.getRepositoryManager().addRepository(repository); + return repository; + } + + protected RepositoryQuery createQuery(TaskRepository repository) { + RepositoryQuery query = new RepositoryQuery(repository.getConnectorKind(), repository.getConnectorKind() + + repository.getRepositoryUrl() + Math.random()); + query.setRepositoryUrl(repository.getRepositoryUrl()); + TasksUiPlugin.getTaskList().addQuery(query); + return query; + } + + private IWizardContainer createWizard(CompleteConnectorMigrationWizard wiz) { + wizard = spy(wiz); + WizardDialog dialog = new WizardDialog(WorkbenchUtil.getShell(), wizard); + dialog.create(); + IWizardContainer container = spy(wizard.getContainer()); + when(wizard.getContainer()).thenReturn(container); + return container; + } + + private void spinEventLoop() { + while (Display.getCurrent().readAndDispatch()) { + } + } +} diff --git a/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUiTest.java b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUiTest.java new file mode 100644 index 000000000..18330c454 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUiTest.java @@ -0,0 +1,272 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Tasktop EULA + * which accompanies this distribution, and is available at + * http://tasktop.com/legal + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.ui.migrator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.window.Window; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.mylyn.internal.commons.notifications.feed.ServiceMessage; +import org.eclipse.mylyn.internal.tasks.ui.TaskListBackupManager; +import org.eclipse.mylyn.internal.tasks.ui.migrator.ConnectorMigrationUi.CompleteMigrationJob; +import org.eclipse.mylyn.internal.tasks.ui.notifications.TaskListServiceMessageControl; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskListView; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.swt.widgets.Display; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class ConnectorMigrationUiTest { + + private ConnectorMigrator migrator; + + private final TaskListView taskList = mock(TaskListView.class); + + private final TaskListBackupManager backupManager = mock(TaskListBackupManager.class); + + private final ConnectorMigrationUi migrationUi = spy( + new ConnectorMigrationUi(taskList, backupManager, new DefaultTasksState())); + + private CompleteMigrationJob completeMigrationJob; + + @SuppressWarnings("unchecked") + @Before + public void setUp() { + doNothing().when(migrationUi).warnOfValidationFailure((List<TaskRepository>) any(List.class)); + doNothing().when(migrationUi).notifyMigrationComplete(); + } + + @After + public void tearDown() { + if (completeMigrationJob != null) { + completeMigrationJob.dispose(); + // otherwise it will keep rescheduling itself and interfere with other tests + } + } + + @Test + public void promptToMigrate() { + ServiceMessage message = openMigrationPrompt(Window.OK); + + assertEquals("End of Connector Support", message.getTitle()); + assertTrue(message.getDescription().contains("<a href=\"migrate\">Click here</a>")); + assertEquals(Dialog.DLG_IMG_MESSAGE_INFO, message.getImage()); + assertFalse(message.openLink("foo")); + verify(migrationUi, never()).createMigrationWizard(any(ConnectorMigrator.class)); + verify(migrationUi, never()).createPromptToCompleteMigrationJob(any(ConnectorMigrator.class)); + + message.openLink("migrate"); + } + + @Test + public void finishMigrationWizard() throws Exception { + ServiceMessage message = openMigrationPrompt(Window.OK); + assertTrue(message.openLink("migrate")); + verify(migrationUi).createPromptToCompleteMigrationJob(any(ConnectorMigrator.class)); + } + + @Test + public void cancelMigrationWizard() throws Exception { + ServiceMessage message = openMigrationPrompt(Window.CANCEL); + assertFalse(message.openLink("migrate")); + verify(migrationUi, never()).createPromptToCompleteMigrationJob(any(ConnectorMigrator.class)); + } + + @Test + public void promptToMigrateOpensSecondMessage() { + ServiceMessage message = openMigrationPrompt(Window.OK); + Job job = mock(Job.class); + doReturn(job).when(migrationUi).createPromptToCompleteMigrationJob(any(ConnectorMigrator.class)); + + message.openLink("migrate"); + verify(migrationUi).createMigrationWizard(any(ConnectorMigrator.class)); + verify(migrationUi).createPromptToCompleteMigrationJob(any(ConnectorMigrator.class)); + verify(job).schedule(); + } + + @Test + public void promptToCompleteMigration() throws InterruptedException { + ServiceMessage message = openCompleteMigrationPrompt(Window.OK); + + assertEquals("Connector Migration", message.getTitle()); + assertTrue(message.getDescription().contains("<a href=\"complete-migration\">complete migration</a>")); + assertEquals(Dialog.DLG_IMG_MESSAGE_WARNING, message.getImage()); + assertFalse(message.openLink("foo")); + verify(migrationUi, never()).createCompleteMigrationWizard(any(ConnectorMigrator.class)); + + message.openLink("complete-migration"); + verify(migrationUi).createCompleteMigrationWizard(any(ConnectorMigrator.class)); + } + + @Test + public void finishCompleteMigrationWizard() throws Exception { + ServiceMessage message = openCompleteMigrationPrompt(Window.OK); + assertTrue(message.openLink("complete-migration")); + } + + @Test + public void cancelCompleteMigrationWizard() throws Exception { + ServiceMessage message = openCompleteMigrationPrompt(Window.CANCEL); + assertFalse(message.openLink("complete-migration")); + } + + @Test + public void secondMessageReopensAfterDelay() throws Exception { + ServiceMessage message = openCompleteMigrationPrompt(Window.OK); + TaskListServiceMessageControl messageControl = createMessageControl(); + + Thread.sleep(2100); + ServiceMessage message2 = captureMessage(messageControl); + assertTrue(message2 != message); + + Thread.sleep(2100); + ServiceMessage message3 = captureMessage(messageControl); + assertTrue(message3 != message2); + + message3.openLink("complete-migration"); + Thread.sleep(2100); + ServiceMessage message4 = captureMessage(messageControl); + assertTrue(message4 == message3); + } + + @Test + public void backupTaskList() throws Exception { + ImmutableMap<String, String> kinds = ImmutableMap.of("mock", "mock.new"); + TaskRepository repository = new TaskRepository("mock", "http://mock"); + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, ImmutableSet.of(repository))); + IProgressMonitor monitor = mock(IProgressMonitor.class); + migrationUi.backupTaskList(monitor); + InOrder inOrder = inOrder(backupManager, migrationUi, migrator, monitor); + inOrder.verify(monitor).subTask("Backing up task list"); + inOrder.verify(migrationUi).getBackupFileName(any(Date.class)); + inOrder.verify(backupManager).backupNow(true); + inOrder.verify(monitor).worked(1); + } + + @Test + public void getBackupFileName() throws Exception { + Date date = new Date(); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.set(Calendar.MILLISECOND, 0); + date = cal.getTime(); + String fileName = migrationUi.getBackupFileName(date); + Matcher m = Pattern.compile("connector-migration-(\\d{4}_\\d{2}_\\d{2}_\\d{6}).zip").matcher(fileName); + Matcher dateFormatMatcher = Pattern.compile("\\d{4}-\\d{2}-\\d{2}-\\d{6}.zip").matcher(fileName); + Matcher oldDateFormatMatcher = Pattern.compile("\\d{4}-\\d{2}-\\d{2}").matcher(fileName); + assertTrue(m.matches()); + assertFalse(dateFormatMatcher.find()); + assertFalse(oldDateFormatMatcher.find()); + Date fileNameTime = new SimpleDateFormat("yyyy_MM_dd_HHmmss").parse(m.group(1)); + assertEquals(date, fileNameTime); + } + + /** + * @param returnCode + * the return code of the wizard that opens when the link in the prompt is clicked + */ + private ServiceMessage openMigrationPrompt(int returnCode) { + TaskListServiceMessageControl messageControl = createMessageControl(); + migrator = spy( + new ConnectorMigrator(ImmutableMap.of("mock", "mock.new"), "", new DefaultTasksState(), migrationUi)); + WizardDialog wizard = mock(WizardDialog.class); + when(wizard.open()).thenReturn(returnCode); + doReturn(wizard).when(migrationUi).createMigrationWizard(any(ConnectorMigrator.class)); + migrationUi.promptToMigrate(migrator); + return captureMessage(messageControl); + } + + /** + * @param returnCode + * the return code of the wizard that opens when the link in the prompt is clicked + */ + private ServiceMessage openCompleteMigrationPrompt(int returnCode) throws InterruptedException { + TaskListServiceMessageControl messageControl = createMessageControl(); + migrator = spy( + new ConnectorMigrator(ImmutableMap.of("mock", "mock.new"), "", new DefaultTasksState(), migrationUi)); + when(migrationUi.getCompletionPromptFrequency()).thenReturn(2); + WizardDialog wizard = mock(WizardDialog.class); + when(wizard.open()).thenReturn(returnCode); + doReturn(wizard).when(migrationUi).createCompleteMigrationWizard(any(ConnectorMigrator.class)); + completeMigrationJob = (CompleteMigrationJob) migrationUi.createPromptToCompleteMigrationJob(migrator); + assertTrue(completeMigrationJob.isSystem()); + assertFalse(completeMigrationJob.isUser()); + completeMigrationJob.schedule(); + completeMigrationJob.join(); + return captureMessage(messageControl); + } + + private TaskListServiceMessageControl createMessageControl() { + TaskListServiceMessageControl messageControl = mock(TaskListServiceMessageControl.class); + when(taskList.getServiceMessageControl()).thenReturn(messageControl); + return messageControl; + } + + private ServiceMessage captureMessage(TaskListServiceMessageControl messageControl) { + spinEventLoop(); + ArgumentCaptor<ServiceMessage> messageCaptor = ArgumentCaptor.forClass(ServiceMessage.class); + verify(messageControl, atLeastOnce()).setMessage(messageCaptor.capture()); + ServiceMessage message = messageCaptor.getValue(); + return message; + } + + private void spinEventLoop() { + while (Display.getCurrent().readAndDispatch()) { + } + } + + private ConnectorMigrator createMigrator(boolean hasOldConnector, boolean hasNewConnector, + Map<String, String> kinds, Set<TaskRepository> repositories) { + IRepositoryManager manager = mock(IRepositoryManager.class); + AbstractRepositoryConnector connector = mock(AbstractRepositoryConnector.class); + ConnectorMigrator migrator = new ConnectorMigrator(kinds, "", new DefaultTasksState(), migrationUi); + when(manager.getRepositories("mock")).thenReturn(repositories); + if (hasOldConnector) { + when(manager.getRepositoryConnector("mock")).thenReturn(connector); + when(manager.getRepositoryConnector("kind")).thenReturn(connector); + } + if (hasNewConnector) { + when(manager.getRepositoryConnector("mock.new")).thenReturn(connector); + when(manager.getRepositoryConnector("kind.new")).thenReturn(connector); + } + return migrator; + } +} diff --git a/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizardTest.java b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizardTest.java new file mode 100644 index 000000000..8ffc39009 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizardTest.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Tasktop EULA + * which accompanies this distribution, and is available at + * http://tasktop.com/legal + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.ui.migrator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.mylyn.commons.workbench.WorkbenchUtil; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskListView; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.PlatformUI; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class ConnectorMigrationWizardTest { + public class TestConnectorMigrationWizard extends ConnectorMigrationWizard { + private TestConnectorMigrationWizard(ConnectorMigrator migrator) { + super(migrator); + } + + @Override + protected CheckboxTreeViewer createConnectorList(Composite parent, List<String> kinds) { + return new CheckboxTreeViewer(parent) { + { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + fireCheckStateChanged(null); + } + }); + } + + @Override + public Object[] getCheckedElements() { + return new String[] { "foo", "bar" }; + } + }; + } + } + + private ConnectorMigrationWizard wizard; + + private final ConnectorMigrationUi migrationUi = spy(new ConnectorMigrationUi( + TaskListView.getFromActivePerspective(), TasksUiPlugin.getBackupManager(), new DefaultTasksState())); + + private ConnectorMigrator migrator; + + @SuppressWarnings("unchecked") + @Before + public void setUp() { + doNothing().when(migrationUi).warnOfValidationFailure((List<TaskRepository>) any(List.class)); + doNothing().when(migrationUi).notifyMigrationComplete(); + createMigrator(ImmutableMap.of("mock", "mock.new")); + } + + @Test + public void addPages() { + createWizard(new ConnectorMigrationWizard(migrator)); + assertEquals(2, wizard.getPageCount()); + } + + @Test + public void firstPage() { + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + assertEquals("End of Connector Support", firstPage.getTitle()); + assertEquals( + "Support is ending for some connectors, but replacement connectors are installed. This wizard will help you " + + "migrate your configuration and data to the new connectors.", + firstPage.getMessage()); + assertTrue(firstPage.getControl() instanceof Composite); + Composite control = (Composite) firstPage.getControl(); + assertEquals(1, control.getChildren().length); + assertTrue(control.getChildren()[0] instanceof Link); + } + + @Test + public void secondPage() { + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + IWizardPage secondPage = firstPage.getNextPage(); + assertEquals("Select Connectors", secondPage.getTitle()); + assertEquals( + "Select the connectors to migrate. Your task list and repositories will be backed up before migration; you can " + + "undo the migration by selecting \"Restore Tasks from History\" in the Task List view " + + "menu and choosing the " + + "connector-migration-*.zip file stored in <workspace>/.metadata/.mylyn/backup.", + secondPage.getDescription()); + assertTrue(secondPage.getControl() instanceof Composite); + Composite control = (Composite) secondPage.getControl(); + assertEquals(1, control.getChildren().length); + assertTrue(control.getChildren()[0] instanceof Tree); + } + + @Test + public void performFinishAfterConnectorsSelected() + throws InvocationTargetException, InterruptedException, IOException { + createMigrator(ImmutableMap.of("foo", "foo.new", "bar", "bar.new", "baz", "baz.new")); + IWizardContainer container = createWizard(new TestConnectorMigrationWizard(migrator)); + spinEventLoop(); + wizard.performFinish(); + verify(container).run(eq(true), eq(true), any(IRunnableWithProgress.class)); + verify(migrator).setConnectorsToMigrate(eq(ImmutableList.of("foo", "bar"))); + verify(migrator).migrateConnectors(any(IProgressMonitor.class)); + } + + protected void createMigrator(Map<String, String> connectors) { + TaskRepositoryManager manager = spy(new TaskRepositoryManager()); + createMigrator(connectors, manager); + } + + private void createMigrator(Map<String, String> connectors, TaskRepositoryManager manager) { + DefaultTasksState tasksState = spy(new DefaultTasksState()); + when(tasksState.getRepositoryManager()).thenReturn(manager); + migrator = spy(new ConnectorMigrator(connectors, "", tasksState, migrationUi)); + } + + @Test + public void performFinishNoConnectorsSelectedByDefault() + throws InvocationTargetException, InterruptedException, IOException { + createMigrator(ImmutableMap.of("foo", "foo.new", "bar", "bar.new", "baz", "baz.new")); + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + wizard.performFinish(); + verify(container).run(eq(true), eq(true), any(IRunnableWithProgress.class)); + verify(migrator).setConnectorsToMigrate(eq(ImmutableList.<String> of())); + verify(migrator).migrateConnectors(any(IProgressMonitor.class)); + } + + @Test + public void performFinishSelectsRelevantConnectors() + throws InvocationTargetException, InterruptedException, IOException { + TaskRepositoryManager manager = spy(new TaskRepositoryManager()); + + createAndAddConnector(manager, "mock", "Mock Connector"); + createAndAddConnector(manager, "foo", "Foo Connector"); + createAndAddConnector(manager, "bar", "Bar Connector"); + + manager.addRepository(new TaskRepository("mock", "http://mock")); + manager.addRepository(new TaskRepository("bar", "http://bar")); + + createMigrator(ImmutableMap.of("foo", "foo.new", "bar", "bar.new", "mock", "mock.new"), manager); + + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + wizard.performFinish(); + verify(container).run(eq(true), eq(true), any(IRunnableWithProgress.class)); + verify(migrator).setConnectorsToMigrate(eq(ImmutableList.of("bar", "mock"))); + verify(migrator).migrateConnectors(any(IProgressMonitor.class)); + } + + private void createAndAddConnector(TaskRepositoryManager manager, String kind, String label) { + AbstractRepositoryConnector mockConnector = mock(AbstractRepositoryConnector.class); + when(mockConnector.getLabel()).thenReturn(label); + when(manager.getRepositoryConnector(kind)).thenReturn(mockConnector); + } + + @Test + public void performFinishSetsErrorMessage() throws InvocationTargetException, InterruptedException, IOException { + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + doThrow(new InvocationTargetException(new IOException("Backup failed"))).when(container).run(any(Boolean.class), + any(Boolean.class), any(IRunnableWithProgress.class)); + wizard.performFinish(); + assertEquals("Backup failed", container.getCurrentPage().getErrorMessage()); + } + + @Test + public void isPageComplete() throws Exception { + IWizardContainer container = createWizard(new ConnectorMigrationWizard(migrator)); + IWizardPage firstPage = container.getCurrentPage(); + IWizardPage secondPage = firstPage.getNextPage(); + assertTrue(firstPage.isPageComplete()); + assertFalse(secondPage.isPageComplete()); + + container.showPage(secondPage); + assertTrue(firstPage.isPageComplete()); + assertTrue(secondPage.isPageComplete()); + } + + @Test + public void createConnectorList() throws Exception { + CheckboxTreeViewer viewer = new ConnectorMigrationWizard(migrator).createConnectorList(WorkbenchUtil.getShell(), + ImmutableList.of("mock")); + IRepositoryManager manager = migrator.getRepositoryManager(); + assertEquals(ImmutableList.of("mock"), viewer.getInput()); + assertTrue(viewer.getLabelProvider() instanceof LabelProvider); + assertEquals("mock", ((LabelProvider) viewer.getLabelProvider()).getText("mock")); + + AbstractRepositoryConnector connector = mock(AbstractRepositoryConnector.class); + when(connector.getLabel()).thenReturn("My Connector"); + when(manager.getRepositoryConnector("mock")).thenReturn(connector); + manager.addRepository(new TaskRepository("mock", "http://mock")); + manager.addRepository(new TaskRepository("mock", "http://mock2")); + assertEquals("My Connector (used by 2 repositories)", + ((LabelProvider) viewer.getLabelProvider()).getText("mock")); + } + + private IWizardContainer createWizard(ConnectorMigrationWizard wiz) { + wizard = spy(wiz); + WizardDialog dialog = new WizardDialog(WorkbenchUtil.getShell(), wizard); + dialog.create(); + IWizardContainer container = spy(wizard.getContainer()); + when(wizard.getContainer()).thenReturn(container); + return container; + } + + private void spinEventLoop() { + while (Display.getCurrent().readAndDispatch()) { + } + } +} diff --git a/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigratorTest.java b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigratorTest.java new file mode 100644 index 000000000..01851b881 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui.tests/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigratorTest.java @@ -0,0 +1,607 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; +import org.eclipse.mylyn.internal.tasks.core.DateRange; +import org.eclipse.mylyn.internal.tasks.core.IRepositoryConstants; +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.TaskActivityManager; +import org.eclipse.mylyn.internal.tasks.core.TaskCategory; +import org.eclipse.mylyn.internal.tasks.core.TaskJobFactory; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager; +import org.eclipse.mylyn.internal.tasks.core.TaskTask; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.internal.tasks.ui.TaskListBackupManager; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskListView; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.context.AbstractTaskContextStore; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tests.util.TestFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class ConnectorMigratorTest { + public class SpyTasksState extends DefaultTasksState { + + TaskActivityManager taskActivityManager = spy(super.getTaskActivityManager()); + + TaskDataManager taskDataManager = spy(super.getTaskDataManager()); + + RepositoryModel repositoryModel = spy(super.getRepositoryModel()); + + TaskList taskList = spy(super.getTaskList()); + + AbstractTaskContextStore contextStore = spy(super.getContextStore()); + + TaskJobFactory taskJobFactory = spy(super.getTaskJobFactory()); + + TaskRepositoryManager repositoryManager = spy(super.getRepositoryManager()); + + @Override + public TaskActivityManager getTaskActivityManager() { + return taskActivityManager; + } + + @Override + public TaskDataManager getTaskDataManager() { + return taskDataManager; + } + + @Override + public RepositoryModel getRepositoryModel() { + return repositoryModel; + } + + @Override + public TaskList getTaskList() { + return taskList; + } + + @Override + public AbstractTaskContextStore getContextStore() { + return contextStore; + } + + @Override + public TaskJobFactory getTaskJobFactory() { + return taskJobFactory; + } + + @Override + public TaskRepositoryManager getRepositoryManager() { + return repositoryManager; + } + + } + + private final ImmutableMap<String, String> kinds = ImmutableMap.of("mock", "mock.new"); + + private final TaskRepository repository = new TaskRepository("mock", "http://mock"); + + private final ImmutableSet<TaskRepository> singleRepository = ImmutableSet.of(repository); + + private TaskRepository migratedRepository = new TaskRepository("mock.new", "http://mock"); + + private final AuthenticationCredentials repoCreds = new AuthenticationCredentials("u1", "p1"); + + private final AuthenticationCredentials proxyCreds = new AuthenticationCredentials("u2", "p2"); + + private final AuthenticationCredentials httpCreds = new AuthenticationCredentials("u3", "p3"); + + private final AuthenticationCredentials certCreds = new AuthenticationCredentials("u4", "p4"); + + private final AbstractRepositoryConnector connector = mock(AbstractRepositoryConnector.class); + + private final AbstractRepositoryConnector newConnector = mock(AbstractRepositoryConnector.class); + + private final TaskListView taskList = mock(TaskListView.class); + + private final TaskListBackupManager backupManager = mock(TaskListBackupManager.class); + + DefaultTasksState tasksState = spy(new SpyTasksState()); + + private TaskRepositoryManager manager = tasksState.getRepositoryManager(); + + private final ConnectorMigrationUi migrationUi = spy(new ConnectorMigrationUi(taskList, backupManager, tasksState)); + + @SuppressWarnings("unchecked") + @Before + public void setUp() { + doNothing().when(migrationUi).warnOfValidationFailure((List<TaskRepository>) any(List.class)); + doNothing().when(migrationUi).notifyMigrationComplete(); + } + + @After + public void tearDown() throws Exception { + TestFixture.resetTaskList(); + new File("test-context.zip").delete(); + } + + @Test + public void setConnectorsToMigrate() throws Exception { + ConnectorMigrator migrator = createMigrator(true, true, ImmutableMap.of("mock", "mock.new", "kind", "kind.new"), + ImmutableSet.of(repository, new TaskRepository("kind", "http://mock")), true); + try { + migrator.setConnectorsToMigrate(ImmutableList.of("foo")); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {// NOSONAR + } + migrator.setConnectorsToMigrate(ImmutableList.of("kind")); + assertEquals(ImmutableMap.of("kind", "kind.new"), migrator.getSelectedConnectors()); + } + + @SuppressWarnings("unchecked") + @Test + public void migrateConnectors() throws Exception { + assertMigrateConnectors(); + verify(migrationUi, never()).warnOfValidationFailure((List<TaskRepository>) any()); + } + + @SuppressWarnings("unchecked") + @Test + public void migrateConnectorsValidationFailure() throws Exception { + when(newConnector.validateRepository(any(TaskRepository.class), any(IProgressMonitor.class))) + .thenThrow(CoreException.class); + assertMigrateConnectors(); + verify(migrationUi).warnOfValidationFailure(ImmutableList.of(migratedRepository)); + } + + @Test + public void cancelMigration() throws Exception { + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, singleRepository, true)); + IProgressMonitor monitor = new NullProgressMonitor(); + monitor.setCanceled(true); + IProgressMonitor spyMonitor = spy(monitor); + try { + migrator.setConnectorsToMigrate(ImmutableList.of("mock")); + migrator.migrateConnectors(spyMonitor); + fail("Expected OperationCanceledException"); + } catch (OperationCanceledException e) {// NOSONAR + } + verify(spyMonitor).beginTask("Migrating repositories", 2); + verify(spyMonitor).isCanceled(); + verify(manager, never()).getRepositoryConnector(any(String.class)); + verify(manager, never()).addRepository(any(TaskRepository.class)); + verify(migrator, never()).getMigratedRepository(any(String.class), any(TaskRepository.class)); + } + + private ConnectorMigrator assertMigrateConnectors() throws CoreException, IOException { + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, singleRepository, true)); + IProgressMonitor monitor = migrateConnectors(migrator); + + assertTrue(repository.isOffline()); + assertEquals("http://mock (Unsupported, do not delete)", repository.getRepositoryLabel()); + InOrder inOrder = inOrder(manager, newConnector, migrationUi, migrator, monitor); + inOrder.verify(monitor).beginTask("Migrating repositories", 2); + + inOrder.verify(migrationUi).backupTaskList(monitor); + inOrder.verify(monitor).subTask("Backing up task list"); + inOrder.verify(monitor).worked(1); + + inOrder.verify(monitor).subTask("Migrating http://mock"); + inOrder.verify(migrator).migrateRepository("mock.new", "http://mock", repository); + inOrder.verify(manager).addRepository(migratedRepository); + inOrder.verify(migrator).disconnect(repository); + inOrder.verify(monitor).worked(1); + inOrder.verify(monitor).beginTask("Validating repository connections", 1); + inOrder.verify(monitor).subTask("Validating connection to http://mock"); + inOrder.verify(newConnector).validateRepository(migratedRepository, monitor); + inOrder.verify(monitor).done(); + verifyNoMoreInteractions(newConnector); + return migrator; + } + + private IProgressMonitor migrateConnectors(ConnectorMigrator migrator) throws IOException { + IProgressMonitor monitor = mock(IProgressMonitor.class); + migrator.setConnectorsToMigrate(ImmutableList.of("mock")); + migrator.migrateConnectors(monitor); + return monitor; + } + + @Test + public void migrateRepository() throws Exception { + ConnectorMigrator migrator = createMigrator(true, true, kinds, singleRepository, true); + populateRepository(); + + migratedRepository = migrator.getMigratedRepository("mock.new", repository); + assertEquals("foovalue", migratedRepository.getProperty("foo")); + assertEquals("barvalue", migratedRepository.getProperty("bar")); + assertNull(migratedRepository.getProperty(IRepositoryConstants.PROPERTY_SYNCTIMESTAMP)); + assertEquals("My Label", migratedRepository.getRepositoryLabel()); + assertEquals("mock.new", migratedRepository.getConnectorKind()); + + assertEquals(repoCreds, migratedRepository.getCredentials(AuthenticationType.REPOSITORY)); + assertEquals(proxyCreds, migratedRepository.getCredentials(AuthenticationType.PROXY)); + assertEquals(httpCreds, migratedRepository.getCredentials(AuthenticationType.HTTP)); + assertEquals(certCreds, migratedRepository.getCredentials(AuthenticationType.CERTIFICATE)); + + assertTrue(migratedRepository.getSavePassword(AuthenticationType.REPOSITORY)); + assertTrue(migratedRepository.getSavePassword(AuthenticationType.PROXY)); + assertFalse(migratedRepository.getSavePassword(AuthenticationType.HTTP)); + assertFalse(migratedRepository.getSavePassword(AuthenticationType.CERTIFICATE)); + } + + @Test + public void migrateExistingRepositoryDoesNothing() throws Exception { + migratedRepository.setRepositoryLabel("My Old Label"); + + ConnectorMigrator migrator = createMigrator(true, true, kinds, singleRepository, true); + when(manager.getRepository("mock.new", "http://mock")).thenReturn(migratedRepository); + populateRepository(); + + assertSame(migratedRepository, migrator.getMigratedRepository("mock.new", repository)); + assertNull(migratedRepository.getProperty("foo")); + assertNull(migratedRepository.getProperty("bar")); + assertNull(migratedRepository.getProperty(IRepositoryConstants.PROPERTY_SYNCTIMESTAMP)); + assertEquals("My Old Label", migratedRepository.getRepositoryLabel()); + assertEquals("mock.new", migratedRepository.getConnectorKind()); + + assertNull(migratedRepository.getCredentials(AuthenticationType.REPOSITORY)); + assertNull(migratedRepository.getCredentials(AuthenticationType.PROXY)); + assertNull(migratedRepository.getCredentials(AuthenticationType.HTTP)); + assertNull(migratedRepository.getCredentials(AuthenticationType.CERTIFICATE)); + + } + + private void populateRepository() { + repository.setProperty("foo", "foovalue"); + repository.setProperty("bar", "barvalue"); + repository.setProperty(IRepositoryConstants.PROPERTY_SYNCTIMESTAMP, "123"); + repository.setRepositoryLabel("My Label"); + repository.setCredentials(AuthenticationType.REPOSITORY, repoCreds, true); + repository.setCredentials(AuthenticationType.PROXY, proxyCreds, true); + repository.setCredentials(AuthenticationType.HTTP, httpCreds, false); + repository.setCredentials(AuthenticationType.CERTIFICATE, certCreds, false); + } + + @Test + public void needsMigrationEmptyKinds() { + try { + createMigrator(true, true, ImmutableMap.<String, String> of(), singleRepository, true); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {// NOSONAR + } + } + + @Test + public void needsMigrationNoRepositories() { + ConnectorMigrator migrator = createMigrator(true, true, kinds, ImmutableSet.<TaskRepository> of(), true); + assertFalse(migrator.needsMigration()); + } + + @Test + public void needsMigrationNoConnectors() { + ConnectorMigrator migrator = createMigrator(false, false, kinds, singleRepository, true); + assertFalse(migrator.needsMigration()); + } + + @Test + public void needsMigrationNoNewConnector() { + ConnectorMigrator migrator = createMigrator(true, false, kinds, singleRepository, true); + assertFalse(migrator.needsMigration()); + } + + @Test + public void needsMigrationNoOldConnector() { + ConnectorMigrator migrator = createMigrator(false, true, kinds, singleRepository, true); + assertFalse(migrator.needsMigration()); + } + + @Test + public void needsMigrationOneRepository() { + ConnectorMigrator migrator = createMigrator(true, true, kinds, singleRepository, true); + assertTrue(migrator.needsMigration()); + } + + @Test + public void needsMigrationMultipleRepositories() { + ConnectorMigrator migrator = createMigrator(true, true, kinds, + ImmutableSet.of(repository, new TaskRepository("mock", "http://mock2")), true); + assertTrue(migrator.needsMigration()); + } + + @Test + public void needsMigrationMultiKindsOneRepository() { + ImmutableMap<String, String> multiKinds = ImmutableMap.of("mock", "mock.new", "kind", "kind.new"); + ConnectorMigrator migrator = createMigrator(true, true, multiKinds, singleRepository, true); + assertTrue(migrator.needsMigration()); + + migrator = createMigrator(true, true, multiKinds, ImmutableSet.of(new TaskRepository("kind", "http://mock")), + true); + assertTrue(migrator.needsMigration()); + } + + @Test + public void needsMigrationMultiKindsMultiRepositories() { + ConnectorMigrator migrator = createMigrator(true, true, ImmutableMap.of("mock", "mock.new", "kind", "kind.new"), + ImmutableSet.of(repository, new TaskRepository("kind", "http://mock")), true); + assertTrue(migrator.needsMigration()); + } + + @Test + public void migrateTasksWaitsForSyncJobs() throws Exception { + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, ImmutableSet.of(repository), false)); + JobListener listener = mock(JobListener.class); + when(listener.isComplete()).thenReturn(false, false, true); + when(migrator.getSyncTaskJobListener()).thenReturn(listener); + migrator.migrateTasks(new NullProgressMonitor()); + verify(listener, times(3)).isComplete(); + } + + @Test + public void migrateTasks() throws Exception { + when(newConnector.getConnectorKind()).thenReturn("mock.new"); + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, ImmutableSet.of(repository), false)); + TaskData taskData2 = new TaskData(mock(TaskAttributeMapper.class), "mock.new", repository.getRepositoryUrl(), + "2.migrated"); + when(migrator.getTaskData(eq("key2"), eq(newConnector), any(TaskRepository.class), any(IProgressMonitor.class))) + .thenReturn(taskData2); + ITask task1 = new TaskTask("mock", "http://mock", "1"); + task1.setTaskKey("key1"); + ((AbstractTask) task1).setSynchronizationState(SynchronizationState.INCOMING_NEW); + ITask task2 = new TaskTask("mock", "http://mock", "2"); + task2.setTaskKey("key2"); + ((AbstractTask) task2).setSynchronizationState(SynchronizationState.INCOMING); + ITask task1Migrated = new TaskTask("mock.new", "http://mock", "1.migrated"); + task1Migrated.setTaskKey("key1"); + TaskTask taskOtherRepo = new TaskTask("mock", "http://other-mock", "1"); + tasksState.getTaskList().addTask(task1); + tasksState.getTaskList().addTask(task1Migrated); + tasksState.getTaskList().addTask(taskOtherRepo); + tasksState.getTaskList().addTask(task2); + RepositoryQuery query = new RepositoryQuery("mock", "mock"); + query.setRepositoryUrl("http://mock"); + tasksState.getTaskList().addQuery(query); + migrateConnectors(migrator); + NullProgressMonitor monitor = new NullProgressMonitor(); + + migrator.migrateTasks(monitor); + verify(tasksState.getTaskActivityManager()).deactivateActiveTask(); + TaskRepository newRepository = manager.getRepository("mock.new", "http://mock"); + verify(migrator).migrateTasks(ImmutableSet.of(task1, task2), repository, newRepository, + manager.getRepositoryConnector("mock.new"), monitor); + + verify(migrator, never()).getTaskData("key1", newConnector, newRepository, monitor); + verify(migrator, never()).createTask(argThat(not(taskData2)), any(TaskRepository.class)); + verify(migrator).getTaskData("key2", newConnector, newRepository, monitor); + verify(migrator).createTask(taskData2, newRepository); + verify(migrator).migratePrivateData((AbstractTask) task1, (AbstractTask) task1Migrated, monitor); + ITask task2Migrated = new TaskTask("mock.new", "http://mock", "2.migrated"); + task2Migrated.setTaskKey("key2"); + verify(migrator).migratePrivateData((AbstractTask) task2, (AbstractTask) task2Migrated, monitor); + + verify(migrationUi).delete(ImmutableSet.of(task1, task2), repository, newRepository, monitor); + assertEquals(SynchronizationState.INCOMING_NEW, tasksState.getTaskList() + .getTask(repository.getRepositoryUrl(), "1.migrated") + .getSynchronizationState()); + assertEquals(SynchronizationState.INCOMING, tasksState.getTaskList() + .getTask(repository.getRepositoryUrl(), "2.migrated") + .getSynchronizationState()); + + assertEquals(ImmutableSet.of(taskOtherRepo, task1Migrated, task2Migrated), + ImmutableSet.copyOf(tasksState.getTaskList().getAllTasks())); + verify(tasksState.getRepositoryManager()).removeRepository(repository); + assertTrue(tasksState.getTaskList().getQueries().isEmpty()); + assertEquals(ImmutableSet.of(newRepository), tasksState.getRepositoryManager().getRepositories("mock.new")); + } + + @Test + public void migrateTasksSameId() throws Exception { + when(newConnector.getConnectorKind()).thenReturn("mock.new"); + ConnectorMigrator migrator = spy(createMigrator(true, true, kinds, ImmutableSet.of(repository), false)); + TaskData taskData1 = new TaskData(mock(TaskAttributeMapper.class), "mock.new", repository.getRepositoryUrl(), + "1"); + when(migrator.getTaskData(eq("key1"), eq(newConnector), any(TaskRepository.class), any(IProgressMonitor.class))) + .thenReturn(taskData1); + TaskData taskData2 = new TaskData(mock(TaskAttributeMapper.class), "mock.new", repository.getRepositoryUrl(), + "2"); + when(migrator.getTaskData(eq("key2"), eq(newConnector), any(TaskRepository.class), any(IProgressMonitor.class))) + .thenReturn(taskData2); + ITask task1 = new TaskTask("mock", "http://mock", "1"); + task1.setTaskKey("key1"); + ((AbstractTask) task1).setSynchronizationState(SynchronizationState.INCOMING_NEW); + ITask task2 = new TaskTask("mock", "http://mock", "2"); + task2.setTaskKey("key2"); + ((AbstractTask) task2).setSynchronizationState(SynchronizationState.INCOMING); + ITask task1Migrated = new TaskTask("mock.new", "http://mock", "1"); + task1Migrated.setTaskKey("key1"); + TaskTask taskOtherRepo = new TaskTask("mock", "http://other-mock", "1"); + tasksState.getTaskList().addTask(task1); + tasksState.getTaskList().addTask(task1Migrated); + tasksState.getTaskList().addTask(taskOtherRepo); + tasksState.getTaskList().addTask(task2); + RepositoryQuery query = new RepositoryQuery("mock", "mock"); + query.setRepositoryUrl("http://mock"); + tasksState.getTaskList().addQuery(query); + migrateConnectors(migrator); + NullProgressMonitor monitor = new NullProgressMonitor(); + + migrator.migrateTasks(monitor); + verify(tasksState.getTaskActivityManager()).deactivateActiveTask(); + TaskRepository newRepository = manager.getRepository("mock.new", "http://mock"); + verify(migrator).migrateTasks(ImmutableSet.of(task1, task2), repository, newRepository, + manager.getRepositoryConnector("mock.new"), monitor); + + verify(migrator).getTaskData("key1", newConnector, newRepository, monitor); + verify(migrator).getTaskData("key2", newConnector, newRepository, monitor); + verify(migrator).createTask(taskData2, newRepository); + ITask task2Migrated = new TaskTask("mock.new", "http://mock", "2"); + task2Migrated.setTaskKey("key2"); + verify(migrator).migratePrivateData((AbstractTask) task2, (AbstractTask) task2Migrated, monitor); + verify(migrator).migratePrivateData((AbstractTask) task1, (AbstractTask) task1Migrated, monitor); + + verify(migrationUi).delete(ImmutableSet.of(task1, task2), repository, newRepository, monitor); + assertEquals(SynchronizationState.INCOMING_NEW, + tasksState.getTaskList().getTask(repository.getRepositoryUrl(), "1").getSynchronizationState()); + assertEquals(SynchronizationState.INCOMING, + tasksState.getTaskList().getTask(repository.getRepositoryUrl(), "2").getSynchronizationState()); + + assertEquals(ImmutableSet.of(taskOtherRepo, task1Migrated, task2Migrated), + ImmutableSet.copyOf(tasksState.getTaskList().getAllTasks())); + verify(tasksState.getRepositoryManager()).removeRepository(repository); + assertTrue(tasksState.getTaskList().getQueries().isEmpty()); + assertEquals(ImmutableSet.of(newRepository), tasksState.getRepositoryManager().getRepositories("mock.new")); + } + + @Test + public void migratePrivateData() throws Exception { + ConnectorMigrator migrator = createMigrator(true, true, kinds, ImmutableSet.of(repository), false); + AbstractTask oldTask = new TaskTask("mock", "http://mock", "1"); + AbstractTask newTask = new TaskTask("mock.new", "http://mock", "1.migrated"); + + oldTask.setNotes("some notes"); + DateRange scheduledDate = new DateRange(createCalendar(3)); + Calendar dueDate = createCalendar(5); + tasksState.getTaskActivityManager().setScheduledFor(oldTask, scheduledDate); + tasksState.getTaskActivityManager().setDueDate(oldTask, dueDate.getTime()); + oldTask.setEstimatedTimeHours(7); + + migrator.migratePrivateData(oldTask, newTask, new NullProgressMonitor()); + assertEquals("some notes", newTask.getNotes()); + assertEquals(scheduledDate, newTask.getScheduledForDate()); + assertEquals(dueDate.getTime(), newTask.getDueDate()); + assertEquals(7, newTask.getEstimatedTimeHours()); + } + + @Test + public void migrateCategories() throws Exception { + ConnectorMigrator migrator = createMigrator(true, true, kinds, ImmutableSet.of(repository), false); + AbstractTask oldTask1 = new TaskTask("mock", "http://mock", "1"); + AbstractTask oldTask2 = new TaskTask("mock", "http://mock", "2"); + AbstractTask newTask1 = new TaskTask("mock.new", "http://mock", "1.migrated"); + AbstractTask newTask2 = new TaskTask("mock.new", "http://mock", "2.migrated"); + + TaskCategory category1 = new TaskCategory("category1"); + TaskCategory category2 = new TaskCategory("category2"); + tasksState.getTaskList().addCategory(category1); + tasksState.getTaskList().addCategory(category2); + tasksState.getTaskList().addTask(oldTask1, category1); + tasksState.getTaskList().addTask(oldTask2, category2); + + migrator.migratePrivateData(oldTask1, newTask1, new NullProgressMonitor()); + migrator.migratePrivateData(oldTask2, newTask2, new NullProgressMonitor()); + assertEquals(category1, getCategory(newTask1)); + assertEquals(category2, getCategory(newTask2)); + } + + @Test + public void getCategories() throws Exception { + ConnectorMigrator migrator = createMigrator(true, true, kinds, ImmutableSet.of(repository), false); + AbstractTask oldTask1 = new TaskTask("mock", "http://mock", "1"); + AbstractTask oldTask2 = new TaskTask("mock", "http://mock", "2"); + AbstractTask newTask1 = new TaskTask("mock.new", "http://mock", "1.migrated"); + AbstractTask newTask2 = new TaskTask("mock.new", "http://mock", "2.migrated"); + + TaskCategory category1 = new TaskCategory("category1"); + TaskCategory category2 = new TaskCategory("category2"); + tasksState.getTaskList().addCategory(category1); + tasksState.getTaskList().addCategory(category2); + tasksState.getTaskList().addTask(oldTask1, category1); + tasksState.getTaskList().addTask(newTask1, category1); + tasksState.getTaskList().addTask(oldTask2, category2); + tasksState.getTaskList().addTask(newTask2, category2); + tasksState.getTaskList().addTask(new TaskTask("mock.new", "http://mock", "not categorized")); + + ImmutableMap<AbstractTask, TaskCategory> expected = ImmutableMap.of(oldTask1, category1, oldTask2, category2, + newTask1, category1, newTask2, category2); + assertEquals(expected, migrator.getCategories()); + + tasksState.getTaskList().addTask(new TaskTask("mock.new", "http://mock", "3"), category1); + assertEquals(expected, migrator.getCategories()); + } + + private AbstractTaskCategory getCategory(AbstractTask newTask) { + for (AbstractTaskCategory category : tasksState.getTaskList().getCategories()) { + Optional<ITask> task = Iterables.tryFind(category.getChildren(), Predicates.<ITask> equalTo(newTask)); + if (task.isPresent()) { + return category; + } + } + return null; + } + + private Calendar createCalendar(int daysInFuture) { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_MONTH, daysInFuture); + return cal; + } + + private ConnectorMigrator createMigrator(boolean hasOldConnector, boolean hasNewConnector, + Map<String, String> kinds, Set<TaskRepository> repositories, boolean mockManager) { + if (mockManager) { + manager = mock(TaskRepositoryManager.class); + } + when(tasksState.getRepositoryManager()).thenReturn(manager); + ConnectorMigrator migrator = new ConnectorMigrator(kinds, "", tasksState, migrationUi); + when(manager.getRepositories("mock")).thenReturn(repositories); + if (hasOldConnector) { + when(manager.getRepositoryConnector("mock")).thenReturn(connector); + when(manager.getRepositoryConnector("kind")).thenReturn(connector); + } + if (hasNewConnector) { + when(manager.getRepositoryConnector("mock.new")).thenReturn(newConnector); + when(manager.getRepositoryConnector("kind.new")).thenReturn(newConnector); + } + return migrator; + } + +} diff --git a/org.eclipse.mylyn.tasks.ui/META-INF/MANIFEST.MF b/org.eclipse.mylyn.tasks.ui/META-INF/MANIFEST.MF index 377f277c6..0e1d7e200 100644 --- a/org.eclipse.mylyn.tasks.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.mylyn.tasks.ui/META-INF/MANIFEST.MF @@ -44,6 +44,7 @@ Export-Package: org.eclipse.mylyn.internal.provisional.tasks.ui.wizards;x-intern org.eclipse.mylyn.internal.tasks.ui.dialogs;x-internal:=true, org.eclipse.mylyn.internal.tasks.ui.editors;x-internal:=true, org.eclipse.mylyn.internal.tasks.ui.editors.outline;x-internal:=true, + org.eclipse.mylyn.internal.tasks.ui.migrator;x-internal:=true, org.eclipse.mylyn.internal.tasks.ui.notifications;x-internal:=true, org.eclipse.mylyn.internal.tasks.ui.preferences;x-internal:=true, org.eclipse.mylyn.internal.tasks.ui.properties;x-internal:=true, diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizard.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizard.java new file mode 100644 index 000000000..a7ef2cd7b --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/CompleteConnectorMigrationWizard.java @@ -0,0 +1,254 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import static com.google.common.collect.Iterables.any; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isQueryForConnector; +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isSynchronizing; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskRepositoryLabelProvider; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi; +import org.eclipse.mylyn.tasks.ui.TaskElementLabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +public class CompleteConnectorMigrationWizard extends Wizard { + final class MapContentProvider implements ITreeContentProvider { + private final Map<?, ? extends Collection<?>> map; + + private MapContentProvider(Map<?, ? extends Collection<?>> map) { + this.map = map; + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public void dispose() { + } + + @Override + public boolean hasChildren(Object element) { + return map.containsKey(element); + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof Map) { + return ((Map<?, ?>) inputElement).keySet().toArray(); + } + return null; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (map.get(parentElement) == null) { + return null; + } + return map.get(parentElement).toArray(); + } + } + + private final ConnectorMigrator migrator; + + public CompleteConnectorMigrationWizard(ConnectorMigrator migrator) { + this.migrator = migrator; + } + + @Override + public void addPages() { + setWindowTitle(Messages.CompleteConnectorMigrationWizard_Complete_Connector_Migration); + addPage(new WizardPage(Messages.CompleteConnectorMigrationWizard_Migrate_Queries) { + + @Override + public void createControl(Composite parent) { + setTitle(Messages.CompleteConnectorMigrationWizard_Have_You_Recreated_Your_Queries); + setMessage(Messages.CompleteConnectorMigrationWizard_first_page_message, IMessageProvider.INFORMATION); + Composite c = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true).applyTo(c); + Label oldQueriesLabel = new Label(c, SWT.NONE); + oldQueriesLabel.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); + oldQueriesLabel.setText(Messages.CompleteConnectorMigrationWizard_Queries_Using_Old_Connectors); + Label newQueriesLabel = new Label(c, SWT.NONE); + newQueriesLabel.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); + newQueriesLabel.setText(Messages.CompleteConnectorMigrationWizard_Queries_Using_New_Connectors); + createQueryTree(c, createRepositoryQueryMap(migrator.getSelectedConnectors().keySet())); + createQueryTree(c, createRepositoryQueryMap(migrator.getSelectedConnectors().values())); + setControl(c); + } + + }); + addPage(new WizardPage(Messages.CompleteConnectorMigrationWizard_Complete_Migration) { + + @Override + public void createControl(Composite parent) { + setTitle(Messages.CompleteConnectorMigrationWizard_Complete_Migration); + setMessage(Messages.CompleteConnectorMigrationWizard_second_page_message, IMessageProvider.INFORMATION); + Composite c = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().applyTo(c); + Text text = new Text(c, SWT.READ_ONLY | SWT.MULTI | SWT.WRAP); + text.setText(Messages.CompleteConnectorMigrationWizard_second_page_text); + GridDataFactory.fillDefaults() + .align(SWT.FILL, SWT.FILL) + .grab(true, true) + .hint(600, SWT.DEFAULT) + .applyTo(text); + setControl(c); + } + + @Override + public boolean isPageComplete() { + return super.isPageComplete() && isCurrentPage(); + } + }); + } + + protected TreeViewer createQueryTree(Composite parent, + final Map<TaskRepository, ? extends Set<RepositoryQuery>> queries) { + final TreeViewer viewer = new TreeViewer(parent); + GridDataFactory.fillDefaults().grab(false, true).hint(500, SWT.DEFAULT).applyTo(viewer.getControl()); + viewer.setContentProvider(new MapContentProvider(queries)); + viewer.setInput(queries); + viewer.setLabelProvider(new TaskElementLabelProvider() { + private final TaskRepositoryLabelProvider repositoryLabelProvider = new TaskRepositoryLabelProvider(); + + @Override + public Image getImage(Object element) { + if (element instanceof TaskRepository) { + return repositoryLabelProvider.getImage(element); + } + return super.getImage(element); + } + + @Override + public String getText(Object object) { + if (object instanceof TaskRepository) { + return repositoryLabelProvider.getText(object); + } + return super.getText(object); + } + }); + viewer.addDoubleClickListener(new IDoubleClickListener() { + + @Override + public void doubleClick(DoubleClickEvent event) { + if (viewer.getSelection() instanceof IStructuredSelection) { + Object element = ((IStructuredSelection) viewer.getSelection()).getFirstElement(); + if (element instanceof IRepositoryQuery) { + IRepositoryQuery query = (IRepositoryQuery) element; + AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin + .getConnectorUi(query.getConnectorKind()); + TasksUiInternal.openEditQueryDialog(connectorUi, query); + } + } + } + }); + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + viewer.expandAll(); + } + }); + return viewer; + } + + @Override + public boolean performFinish() { + Job job = new Job("Migrating Tasks and Private Data") { + + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask("Completing connector migration", IProgressMonitor.UNKNOWN); + Collection<String> newConnectors = migrator.getSelectedConnectors().values(); + waitForQueriesToSynchronize(newConnectors, monitor); + migrator.migrateTasks(monitor); + return Status.OK_STATUS; + } + + }; + job.setUser(true); + job.setSystem(false); + job.schedule(); + return true; + } + + protected void waitForQueriesToSynchronize(Collection<String> newConnectors, IProgressMonitor monitor) { + monitor.subTask("Waiting for queries to complete synchronization"); + Iterable<RepositoryQuery> queries = Iterables.concat(createRepositoryQueryMap(newConnectors).values()); + long start = System.currentTimeMillis(); + while (any(queries, isSynchronizing()) + && System.currentTimeMillis() - start < MILLISECONDS.convert(20, MINUTES)) { + try { + Thread.sleep(MILLISECONDS.convert(3, SECONDS)); + } catch (InterruptedException e) {// NOSONAR + } + } + } + + protected Map<TaskRepository, Set<RepositoryQuery>> createRepositoryQueryMap(Collection<String> kinds) { + Builder<TaskRepository, Set<RepositoryQuery>> repositories = ImmutableMap.builder(); + for (final String kind : kinds) { + for (TaskRepository repository : migrator.getRepositoryManager().getRepositories(kind)) { + Set<RepositoryQuery> queriesForUrl = TasksUiPlugin.getTaskList() + .getRepositoryQueries(repository.getRepositoryUrl()); + Set<RepositoryQuery> queries = Sets.filter(queriesForUrl, isQueryForConnector(kind)); + if (!queries.isEmpty()) { + repositories.put(repository, queries); + } + } + } + return repositories.build(); + } +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUi.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUi.java new file mode 100644 index 000000000..8c2edcc57 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationUi.java @@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isQueryForRepository; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.commons.workbench.WorkbenchUtil; +import org.eclipse.mylyn.internal.commons.notifications.feed.ServiceMessage; +import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.UnsubmittedTaskContainer; +import org.eclipse.mylyn.internal.tasks.ui.TaskListBackupManager; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.internal.tasks.ui.actions.DeleteAction; +import org.eclipse.mylyn.internal.tasks.ui.util.TaskDataSnapshotOperation; +import org.eclipse.mylyn.internal.tasks.ui.views.TaskListView; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.osgi.util.NLS; +import org.eclipse.ui.PlatformUI; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +public class ConnectorMigrationUi { + + private static final String MIGRATE = "migrate"; //$NON-NLS-1$ + + private static final String COMLETE_MIGRATION = "complete-migration"; //$NON-NLS-1$ + + private final TaskListView taskListView; + + private final TaskListBackupManager backupManager; + + private final TasksState tasksState; + + public ConnectorMigrationUi(TaskListView taskListView, TaskListBackupManager backupManager, TasksState tasksState) { + this.taskListView = taskListView; + this.backupManager = backupManager; + this.tasksState = tasksState; + } + + /** + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ + protected class CompleteMigrationJob extends Job { + private boolean finishedCompleteMigrationWizard; + + private final ConnectorMigrator migrator; + + private CompleteMigrationJob(String name, ConnectorMigrator migrator) { + super(name); + this.migrator = migrator; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + if (!finishedCompleteMigrationWizard) { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (taskListView != null && taskListView.getServiceMessageControl() != null) { + ServiceMessage message = new ServiceMessage("") { + + @Override + public boolean openLink(String link) { + if (link.equals(COMLETE_MIGRATION)) { + if (createCompleteMigrationWizard(migrator).open() == Window.OK) { + finishedCompleteMigrationWizard = true; + return true; + } + } + return false; + } + }; + message.setTitle("Connector Migration"); + message.setDescription(NLS.bind(Messages.ConnectorMigrator_complete_migration_prompt_title, + COMLETE_MIGRATION)); + message.setImage(Dialog.DLG_IMG_MESSAGE_WARNING); + taskListView.getServiceMessageControl().setMessage(message); + } + } + }); + schedule(TimeUnit.MILLISECONDS.convert(getCompletionPromptFrequency(), TimeUnit.SECONDS)); + } + return Status.OK_STATUS; + } + + public void dispose() { + finishedCompleteMigrationWizard = true; + } + } + + public void promptToMigrate(final ConnectorMigrator migrator) { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (taskListView != null && taskListView.getServiceMessageControl() != null) { + ServiceMessage message = new ServiceMessage("") { + @Override + public boolean openLink(String link) { + if (link.equals(MIGRATE)) { + if (createMigrationWizard(migrator).open() == Window.OK) { + createPromptToCompleteMigrationJob(migrator).schedule(); + return true; + } + } + return false; + } + + }; + message.setTitle("End of Connector Support"); + message.setDescription( + NLS.bind(Messages.ConnectorMigrator_complete_migration_prompt_message, MIGRATE)); + message.setImage(Dialog.DLG_IMG_MESSAGE_INFO); + taskListView.getServiceMessageControl().setMessage(message); + } + } + }); + } + + protected Job createPromptToCompleteMigrationJob(ConnectorMigrator migrator) { + Job job = new CompleteMigrationJob("Complete Connector Migration Prompt", migrator); + job.setUser(false); + job.setSystem(true); + return job; + } + + protected WizardDialog createMigrationWizard(ConnectorMigrator migrator) { + return createWizardDialog(new ConnectorMigrationWizard(migrator)); + } + + protected WizardDialog createCompleteMigrationWizard(ConnectorMigrator migrator) { + return createWizardDialog(new CompleteConnectorMigrationWizard(migrator)); + } + + protected WizardDialog createWizardDialog(Wizard wizard) { + WizardDialog dialog = new WizardDialog(WorkbenchUtil.getShell(), wizard); + dialog.create(); + dialog.setBlockOnOpen(true); + return dialog; + } + + /** + * @return the frequency in seconds with which the completion prompt will be shown + */ + protected int getCompletionPromptFrequency() { + return 5; + } + + public void warnOfValidationFailure(final List<TaskRepository> failedValidation) { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + String repositoryList = Joiner.on("\n") //$NON-NLS-1$ + .join(Iterables.transform(failedValidation, repositoryToLabel())); + MessageDialog.openWarning(WorkbenchUtil.getShell(), "Validation Failed", + NLS.bind(Messages.ConnectorMigrationWizard_validation_failed, repositoryList)); + } + }); + } + + private static Function<TaskRepository, String> repositoryToLabel() { + return new Function<TaskRepository, String>() { + @Override + public String apply(TaskRepository repository) { + return repository.getRepositoryLabel(); + } + }; + } + + protected void backupTaskList(final IProgressMonitor monitor) throws IOException { + try { + monitor.subTask("Backing up task list"); + String backupFolder = TasksUiPlugin.getDefault().getBackupFolderPath(); + new File(backupFolder).mkdirs(); + String fileName = getBackupFileName(new Date()); + new TaskDataSnapshotOperation(backupFolder, fileName).run(new SubProgressMonitor(monitor, 1)); + // also take a snapshot because user might try to restore from snapshot + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { + @Override + public void run() { + backupManager.backupNow(true); + } + }); + monitor.worked(1); + } catch (InvocationTargetException e) { + throw (IOException) e.getCause(); + } + } + + protected String getBackupFileName(Date date) { + // we use an underscore in the date format to prevent TaskListBackupManager from thinking this is one of its backups + // and deleting it (TaskListBackupManager.MYLYN_BACKUP_REGEXP matches any string). + return "connector-migration-" + new SimpleDateFormat("yyyy_MM_dd_HHmmss").format(date) + ".zip"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Deletes the given tasks and repository, and all queries associated with the repository, while preserving the + * credentials of <code>newRepository</code>. + * + * @param newRepository + */ + protected void delete(final Set<ITask> tasks, final TaskRepository repository, final TaskRepository newRepository, + IProgressMonitor monitor) { + final Set<RepositoryQuery> queries = Sets.filter(tasksState.getTaskList().getQueries(), + isQueryForRepository(repository)); + final UnsubmittedTaskContainer unsubmitted = tasksState.getTaskList() + .getUnsubmittedContainer(repository.getRepositoryUrl()); + try { + monitor.subTask("Deleting old repository, tasks, and queries"); + try { + DeleteAction.prepareDeletion(tasks); + if (unsubmitted != null) { + DeleteAction.prepareDeletion(unsubmitted.getChildren()); + } + DeleteAction.prepareDeletion(queries); + } catch (Exception e) {// in case an error happens closing editors, we still want to delete + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, e.getMessage(), e)); + } + tasksState.getTaskList().run(new ITaskListRunnable() { + @Override + public void execute(IProgressMonitor monitor) throws CoreException { + for (ITask task : tasks) { + delete(task); + } + if (unsubmitted != null) { + for (ITask task : unsubmitted.getChildren()) { + delete(task); + } + } + DeleteAction.performDeletion(queries); + Map<AuthenticationType, AuthenticationCredentials> credentialsMap = new HashMap<>(); + for (AuthenticationType type : AuthenticationType.values()) { + AuthenticationCredentials credentials = repository.getCredentials(type); + if (credentials != null) { + credentialsMap.put(type, credentials); + } + } + tasksState.getRepositoryManager().removeRepository(repository); + for (AuthenticationType type : credentialsMap.keySet()) { + newRepository.setCredentials(type, credentialsMap.get(type), + newRepository.getSavePassword(type)); + } + } + }, monitor); + tasksState.getRepositoryModel().clear(); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error deleting task", e)); + } + } + + /** + * Delete a task without deleting the context. + */ + protected void delete(ITask task) { + tasksState.getTaskList().deleteTask(task); + try { + tasksState.getTaskDataManager().deleteTaskData(task); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Failed to delete task data", e));//$NON-NLS-1$ + } + } + + public void notifyMigrationComplete() { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + MessageDialog.openInformation(WorkbenchUtil.getShell(), "Connector Migration Complete", + "Connector migration completed successfully. You may resume using the task list."); + } + }); + } + +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizard.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizard.java new file mode 100644 index 000000000..762a2a7b0 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrationWizard.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.workbench.browser.BrowserUtil; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Link; +import org.eclipse.ui.browser.IWorkbenchBrowserSupport; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; + +public class ConnectorMigrationWizard extends Wizard { + private static class CollectionContentProvider implements ITreeContentProvider { + @Override + public Object[] getChildren(Object parentElement) { + return new Object[0]; + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public boolean hasChildren(Object element) { + return false; + } + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof Collection<?>) { + return ((Collection<?>) inputElement).toArray(); + } + return new Object[0]; + } + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + + private Object[] selectedConnectors = new Object[0]; + + private final ConnectorMigrator migrator; + + public ConnectorMigrationWizard(ConnectorMigrator migrator) { + this.migrator = migrator; + setNeedsProgressMonitor(true); + } + + @Override + public void addPages() { + setWindowTitle(Messages.ConnectorMigrationWizard_Connector_Migration); + addPage(new WizardPage(Messages.ConnectorMigrationWizard_End_of_Connector_Support) { + + @Override + public void createControl(Composite parent) { + setTitle(Messages.ConnectorMigrationWizard_End_of_Connector_Support); + setMessage(Messages.ConnectorMigrationWizard_Message, IMessageProvider.INFORMATION); + Composite c = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().applyTo(c); + Link text = new Link(c, SWT.READ_ONLY | SWT.MULTI | SWT.WRAP); + text.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + BrowserUtil.openUrl(e.text, IWorkbenchBrowserSupport.AS_EXTERNAL); + } + }); + text.setText(NLS.bind(Messages.ConnectorMigrationWizard_Body, migrator.getExplanatoryText())); + GridDataFactory.fillDefaults() + .align(SWT.FILL, SWT.FILL) + .grab(true, true) + .hint(600, SWT.DEFAULT) + .applyTo(text); + setControl(c); + } + }); + addPage(new WizardPage(Messages.ConnectorMigrationWizard_Select_Connectors) { + + @Override + public void createControl(Composite parent) { + setTitle(Messages.ConnectorMigrationWizard_Select_Connectors); + setDescription(Messages.ConnectorMigrationWizard_Select_the_connectors_to_migrate); + Composite c = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().applyTo(c); + List<String> kinds = getRelevantConnectorKinds(migrator.getConnectorKinds().keySet()); + final CheckboxTreeViewer viewer = createConnectorList(c, kinds); + selectedConnectors = kinds.toArray(); + viewer.setCheckedElements(selectedConnectors); + viewer.addCheckStateListener(new ICheckStateListener() { + + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + selectedConnectors = viewer.getCheckedElements(); + setPageComplete(selectedConnectors.length > 0); + } + }); + GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl()); + setControl(c); + } + + @Override + public boolean isPageComplete() { + return super.isPageComplete() && isCurrentPage(); + } + + }); + } + + protected CheckboxTreeViewer createConnectorList(Composite parent, List<String> kinds) { + final CheckboxTreeViewer viewer = new CheckboxTreeViewer(parent); + viewer.setContentProvider(new CollectionContentProvider()); + viewer.setInput(kinds); + viewer.setLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof String) { + String kind = (String) element; + IRepositoryManager manager = migrator.getRepositoryManager(); + AbstractRepositoryConnector connector = manager.getRepositoryConnector(kind); + if (connector != null) { + return connector.getLabel() + + NLS.bind(" (used by {0} repositories)", manager.getRepositories(kind).size()); + } + } + return super.getText(element); + } + }); + return viewer; + } + + private List<String> getRelevantConnectorKinds(Set<String> connectorKinds) { + IRepositoryManager manager = migrator.getRepositoryManager(); + List<String> relevantConnectorKinds = new LinkedList<>(); + for (String connectorKind : connectorKinds) { + if (!manager.getRepositories(connectorKind).isEmpty()) { + relevantConnectorKinds.add(connectorKind); + } + } + return relevantConnectorKinds; + } + + @Override + public boolean performFinish() { + try { + getContainer().run(true, true, new IRunnableWithProgress() { + + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + ImmutableList<String> connectors = FluentIterable.from(ImmutableList.copyOf(selectedConnectors)) + .filter(String.class) + .toList(); + try { + migrator.setConnectorsToMigrate(connectors); + migrator.migrateConnectors(monitor); + } catch (IOException e) { + throw new InvocationTargetException(e); + } + } + }); + } catch (InvocationTargetException | InterruptedException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, e.getMessage(), e)); + IWizardPage page = getContainer().getCurrentPage(); + if (page instanceof WizardPage && e.getCause() != null) { + ((WizardPage) page).setErrorMessage(e.getCause().getMessage()); + } + return false; + } + return true; + } +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrator.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrator.java new file mode 100644 index 000000000..a1d8b47a1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/ConnectorMigrator.java @@ -0,0 +1,426 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Predicates.in; +import static com.google.common.collect.Iterables.any; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isQueryForConnector; +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isTaskForConnector; +import static org.eclipse.mylyn.internal.tasks.ui.migrator.TaskPredicates.isTaskSynchronizing; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; +import org.eclipse.mylyn.internal.tasks.core.IRepositoryConstants; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskTask; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.sync.SynchronizationJob; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.collect.Table; + +/** + * Allows users to migrate their data from an old connector to a new one for the same repository. Performs the following + * steps: + * + * <pre> + * * uses task list message service to prompt users to migrate + * * backs up the task list + * * automatically migrates repositories + * * directs user to manually migrate queries and click a button in the task list message to complete migration + * * once new queries have finished syncing, any tasks that are missing from the new queries are fetched by searching by task key + * * private data (context, notes, categories, scheduled dates, and private due dates) is automatically migrated for all tasks + * * all tasks are marked read except those which were incoming before migration + * * old repositories are deleted + * </pre> + */ +public class ConnectorMigrator { + + private static final ImmutableSet<String> EXCLUDED_REPOSITORY_PROPERTIES = ImmutableSet.of( + IRepositoryConstants.PROPERTY_CONNECTOR_KIND, IRepositoryConstants.PROPERTY_SYNCTIMESTAMP, + IRepositoryConstants.PROPERTY_URL); + + protected static class OldTaskState { + + private final SynchronizationState syncState; + + private final ITask oldTask; + + public OldTaskState(ITask oldTask) { + this.oldTask = oldTask; + this.syncState = oldTask.getSynchronizationState(); + } + + public ITask getOldTask() { + return oldTask; + } + + public SynchronizationState getSyncState() { + return syncState; + } + } + + private final Map<String, String> connectorKinds; + + private final String explanatoryText; + + private final TasksState tasksState; + + private List<String> connectorsToMigrate = ImmutableList.of(); + + private final ConnectorMigrationUi migrationUi; + + private final Map<TaskRepository, TaskRepository> repositories = new HashMap<TaskRepository, TaskRepository>(); + + private final Table<TaskRepository, String, OldTaskState> oldTasksStates = HashBasedTable.create(); + + private Map<ITask, AbstractTaskCategory> categories; + + private final JobListener syncTaskJobListener = new JobListener(new Runnable() { + + @Override + public void run() { + completeMigration(); + } + }); + + public ConnectorMigrator(Map<String, String> connectorKinds, String explanatoryText, TasksState tasksState, + ConnectorMigrationUi migrationUi) { + checkArgument(!connectorKinds.isEmpty()); + this.connectorKinds = connectorKinds; + this.explanatoryText = explanatoryText; + this.migrationUi = migrationUi; + this.tasksState = tasksState; + } + + public Map<String, String> getConnectorKinds() { + return ImmutableMap.copyOf(connectorKinds); + } + + public String getExplanatoryText() { + return explanatoryText; + } + + public boolean needsMigration() { + for (Entry<String, String> entry : connectorKinds.entrySet()) { + String oldKind = entry.getKey(); + String newKind = entry.getValue(); + if (getRepositoryManager().getRepositoryConnector(oldKind) != null + && getRepositoryManager().getRepositoryConnector(newKind) != null + && !getRepositoryManager().getRepositories(oldKind).isEmpty()) { + return true; + } + } + return false; + } + + public void setConnectorsToMigrate(List<String> connectors) { + checkArgument(connectorKinds.keySet().containsAll(connectors)); + this.connectorsToMigrate = ImmutableList.copyOf(connectors); + } + + protected void migrateConnectors(IProgressMonitor monitor) throws IOException { + final List<TaskRepository> failedValidation = new ArrayList<>(); + List<TaskRepository> oldRepositories = gatherRepositoriesToMigrate(connectorsToMigrate); + monitor.beginTask("Migrating repositories", oldRepositories.size() + 1); + getMigrationUi().backupTaskList(monitor); + + for (TaskRepository repository : oldRepositories) { + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + monitor.subTask("Migrating " + repository.getRepositoryLabel()); + String kind = repository.getConnectorKind(); + String newKind = getConnectorKinds().get(kind); + TaskRepository newRepository = getMigratedRepository(newKind, repository); + getRepositoryManager().addRepository(newRepository); + repositories.put(repository, newRepository); + Set<ITask> tasksToMigrate = Sets.filter(getTaskList().getTasks(repository.getRepositoryUrl()), + isTaskForConnector(repository.getConnectorKind())); + for (ITask task : tasksToMigrate) { + oldTasksStates.put(newRepository, task.getTaskKey(), new OldTaskState(task)); + } + disconnect(repository); + monitor.worked(1); + } + + Set<TaskRepository> newRepositories = ImmutableSet.copyOf(repositories.values()); + monitor.beginTask("Validating repository connections", newRepositories.size()); + for (TaskRepository newRepository : newRepositories) { + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + monitor.subTask("Validating connection to " + newRepository.getRepositoryLabel()); + AbstractRepositoryConnector newConnector = getRepositoryManager() + .getRepositoryConnector(newRepository.getConnectorKind()); + try { + newConnector.validateRepository(newRepository, monitor); + } catch (UnsupportedOperationException | CoreException e) { + failedValidation.add(newRepository); + } + monitor.worked(1); + } + + monitor.done(); + + if (!failedValidation.isEmpty()) { + getMigrationUi().warnOfValidationFailure(failedValidation); + } + } + + protected void disconnect(TaskRepository repository) { + repository.setOffline(true); + // we need to change the label so that the new repo doesn't have the same label, so that it can be edited + repository.setRepositoryLabel(repository.getRepositoryLabel() + " (Unsupported, do not delete)"); + Set<RepositoryQuery> queriesForUrl = getTaskList().getRepositoryQueries(repository.getRepositoryUrl()); + for (RepositoryQuery query : Sets.filter(queriesForUrl, isQueryForConnector(repository.getConnectorKind()))) { + query.setAutoUpdate(false);// prevent error logged when Mylyn asks new connector to sync query for old connector + } + } + + protected List<TaskRepository> gatherRepositoriesToMigrate(List<String> connectors) { + List<TaskRepository> oldRepositories = new ArrayList<TaskRepository>(); + for (String kind : connectors) { + oldRepositories.addAll(getRepositoryManager().getRepositories(kind)); + } + return oldRepositories; + } + + protected TaskRepository getMigratedRepository(String newKind, TaskRepository oldRepository) { + String migratedRepositoryUrl = getMigratedRepositoryUrl(oldRepository); + TaskRepository newRepository = getRepositoryManager().getRepository(newKind, migratedRepositoryUrl); + if (newRepository == null) { + newRepository = migrateRepository(newKind, migratedRepositoryUrl, oldRepository); + } + return newRepository; + } + + protected String getMigratedRepositoryUrl(TaskRepository oldRepository) { + return oldRepository.getRepositoryUrl(); + } + + protected TaskRepository migrateRepository(String newKind, String migratedRepositoryUrl, + TaskRepository oldRepository) { + TaskRepository newRepository = new TaskRepository(newKind, migratedRepositoryUrl); + for (Entry<String, String> entry : oldRepository.getProperties().entrySet()) { + if (!EXCLUDED_REPOSITORY_PROPERTIES.contains(entry.getKey())) { + newRepository.setProperty(entry.getKey(), entry.getValue()); + } + } + for (AuthenticationType type : AuthenticationType.values()) { + AuthenticationCredentials credentials = oldRepository.getCredentials(type); + newRepository.setCredentials(type, credentials, oldRepository.getSavePassword(type)); + } + return newRepository; + } + + protected void migrateTasks(IProgressMonitor monitor) { + tasksState.getTaskActivityManager().deactivateActiveTask(); + // Note: we're assuming the new connector uses different task IDs (and therefore different handle identifiers) + // from the old one. This may not be the case for Bugzilla. + for (Entry<TaskRepository, TaskRepository> entry : repositories.entrySet()) { + TaskRepository oldRepository = entry.getKey(); + TaskRepository newRepository = entry.getValue(); + monitor.subTask("Migrating tasks for " + newRepository); + AbstractRepositoryConnector newConnector = getRepositoryManager() + .getRepositoryConnector(newRepository.getConnectorKind()); + Set<ITask> tasksToMigrate = Sets.filter(getTaskList().getTasks(oldRepository.getRepositoryUrl()), + isTaskForConnector(oldRepository.getConnectorKind())); + migrateTasks(tasksToMigrate, oldRepository, newRepository, newConnector, monitor); + } + monitor.subTask("Waiting for tasks to synchronize"); + getSyncTaskJobListener().start(); + while (!getSyncTaskJobListener().isComplete()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, e.getMessage(), e)); + } + } + } + + protected void migrateTasks(final Set<ITask> tasksToMigrate, final TaskRepository oldRepository, + final TaskRepository newRepository, final AbstractRepositoryConnector newConnector, + final IProgressMonitor monitor) { + ImmutableMap<String, ITask> tasksByKey = FluentIterable + .from(getTaskList().getTasks(newRepository.getRepositoryUrl())) + .filter(isTaskForConnector(newConnector.getConnectorKind())) + .uniqueIndex(new Function<ITask, String>() { + @Override + public String apply(ITask task) { + return task.getTaskKey(); + } + }); + final Map<AbstractTask, OldTaskState> migratedTasks = new HashMap<>(); + Set<ITask> tasksToSynchronize = new HashSet<ITask>(); + for (ITask oldTask : tasksToMigrate) { + String taskKey = oldTask.getTaskKey(); + ITask newTask = tasksByKey.get(taskKey); + if (newTask == null) { + TaskData taskData = getTaskData(taskKey, newConnector, newRepository, monitor); + if (taskData != null) { + newTask = createTask(taskData, newRepository); + tasksToSynchronize.add(newTask); + } + } + if (newTask instanceof AbstractTask) { + OldTaskState oldTaskState = oldTasksStates.get(newRepository, oldTask.getTaskKey()); + if (oldTaskState == null) { + oldTaskState = new OldTaskState(oldTask); + } + migratedTasks.put((AbstractTask) newTask, oldTaskState); + } + if (newTask instanceof AbstractTask && oldTask instanceof AbstractTask) { + migratePrivateData((AbstractTask) oldTask, (AbstractTask) newTask, monitor); + } + } + oldTasksStates.row(newRepository).clear(); + migrateTaskContext(migratedTasks); + getMigrationUi().delete(tasksToMigrate, oldRepository, newRepository, monitor); + for (ITask task : tasksToSynchronize) { + getTaskList().addTask(task); + } + SynchronizationJob job = tasksState.getTaskJobFactory().createSynchronizeTasksJob(newConnector, newRepository, + tasksToSynchronize); + getSyncTaskJobListener().add(job, new Runnable() { + @Override + public void run() { + long start = System.currentTimeMillis(); + while (any(migratedTasks.keySet(), isTaskSynchronizing()) + && System.currentTimeMillis() - start < MILLISECONDS.convert(4, HOURS)) { + try { + Thread.sleep(MILLISECONDS.convert(3, SECONDS)); + } catch (InterruptedException e) {// NOSONAR + } + } + for (Entry<AbstractTask, OldTaskState> entry : migratedTasks.entrySet()) { + AbstractTask newTask = entry.getKey(); + OldTaskState oldTask = entry.getValue(); + newTask.setSynchronizationState(oldTask.getSyncState()); + } + Set<RepositoryQuery> queries = getTaskList().getRepositoryQueries(newRepository.getRepositoryUrl()); + if (!queries.isEmpty()) { + SynchronizationJob synchronizeQueriesJob = tasksState.getTaskJobFactory() + .createSynchronizeQueriesJob(newConnector, newRepository, queries); + synchronizeQueriesJob.schedule(); + } + } + }); + job.schedule(); + } + + private void migrateTaskContext(Map<AbstractTask, OldTaskState> taskStates) { + Map<ITask, ITask> tasks = taskStates.entrySet() + .stream() + .collect(Collectors.toMap(e -> e.getValue().getOldTask(), e -> e.getKey())); + TasksUiPlugin.getContextStore().moveContext(tasks); + } + + protected void completeMigration() { + categories = null; + getMigrationUi().notifyMigrationComplete(); + } + + protected void migratePrivateData(AbstractTask oldTask, AbstractTask newTask, IProgressMonitor monitor) { + AbstractTaskCategory category = getCategories().get(oldTask); + if (category != null) { + getTaskList().addTask(newTask, category); + } + newTask.setNotes(oldTask.getNotes()); + tasksState.getTaskActivityManager().setScheduledFor(newTask, oldTask.getScheduledForDate()); + tasksState.getTaskActivityManager().setDueDate(newTask, oldTask.getDueDate()); + newTask.setEstimatedTimeHours(oldTask.getEstimatedTimeHours()); + } + + protected ITask createTask(TaskData taskData, TaskRepository repository) { + return new TaskTask(repository.getConnectorKind(), repository.getRepositoryUrl(), taskData.getTaskId()); + } + + /** + * Connectors may override this method to support migrating tasks that are not contained in any migrated query. + */ + protected TaskData getTaskData(String taskKey, AbstractRepositoryConnector newConnector, + TaskRepository newRepository, IProgressMonitor monitor) { + return null; + } + + public Map<String, String> getSelectedConnectors() { + return Maps.filterKeys(getConnectorKinds(), in(connectorsToMigrate)); + } + + protected TaskList getTaskList() { + return tasksState.getTaskList(); + } + + protected IRepositoryManager getRepositoryManager() { + return tasksState.getRepositoryManager(); + } + + /** + * @return The task categorization that existed the first time this method was called + */ + protected Map<ITask, AbstractTaskCategory> getCategories() { + if (categories == null) { + categories = new HashMap<ITask, AbstractTaskCategory>(); + for (AbstractTaskCategory category : getTaskList().getCategories()) { + for (ITask task : category.getChildren()) { + categories.put(task, category); + } + } + } + return categories; + } + + public ConnectorMigrationUi getMigrationUi() { + return migrationUi; + } + + protected JobListener getSyncTaskJobListener() { + return syncTaskJobListener; + } +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/DefaultTasksState.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/DefaultTasksState.java new file mode 100644 index 000000000..247d64be3 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/DefaultTasksState.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; + +public class DefaultTasksState extends TasksState { + + public DefaultTasksState() { + super(TasksUiPlugin.getTaskList(), TasksUiPlugin.getTaskDataManager(), TasksUiPlugin.getRepositoryManager(), + TasksUiPlugin.getRepositoryModel(), TasksUiPlugin.getContextStore(), + TasksUiPlugin.getTaskActivityManager(), TasksUiPlugin.getTaskJobFactory()); + } + +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/JobListener.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/JobListener.java new file mode 100644 index 000000000..4beae16c5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/JobListener.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; + +public class JobListener { + + private final Set<Job> jobs = Collections.synchronizedSet(new HashSet<Job>()); + + private final Runnable allJobsDone; + + private boolean started; + + private boolean complete; + + public JobListener(Runnable allJobsDone) { + this.allJobsDone = allJobsDone; + } + + /** + * Must be called once when all jobs have been added. + */ + public void start() { + synchronized (jobs) { + started = true; + if (jobs.isEmpty()) { + allJobsDone.run(); + complete = true; + } + } + } + + public boolean isComplete() { + return complete; + } + + /** + * This method should only be called from a single thread. + */ + public void add(final Job job, final Runnable jobDone) { + jobs.add(job); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + jobDone.run(); + synchronized (jobs) { + jobs.remove(job); + if (jobs.isEmpty() && started) { + allJobsDone.run(); + complete = true; + } + } + } + }); + } + +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/Messages.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/Messages.java new file mode 100644 index 000000000..80a779d87 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/Messages.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.tasks.ui.migrator.messages"; //$NON-NLS-1$ + + public static String CompleteConnectorMigrationWizard_Complete_Connector_Migration; + + public static String CompleteConnectorMigrationWizard_Complete_Migration; + + public static String CompleteConnectorMigrationWizard_first_page_message; + + public static String CompleteConnectorMigrationWizard_Have_You_Recreated_Your_Queries; + + public static String CompleteConnectorMigrationWizard_Migrate_Queries; + + public static String CompleteConnectorMigrationWizard_Queries_Using_New_Connectors; + + public static String CompleteConnectorMigrationWizard_Queries_Using_Old_Connectors; + + public static String CompleteConnectorMigrationWizard_second_page_message; + + public static String CompleteConnectorMigrationWizard_second_page_text; + + public static String ConnectorMigrationWizard_Connector_Migration; + + public static String ConnectorMigrationWizard_End_of_Connector_Support; + + public static String ConnectorMigrationWizard_Message; + + public static String ConnectorMigrationWizard_Body; + + public static String ConnectorMigrationWizard_Select_Connectors; + + public static String ConnectorMigrationWizard_Select_the_connectors_to_migrate; + + public static String ConnectorMigrationWizard_validation_failed; + + public static String ConnectorMigrator_complete_migration_prompt_message; + + public static String ConnectorMigrator_complete_migration_prompt_title; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TaskPredicates.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TaskPredicates.java new file mode 100644 index 000000000..277c25d66 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TaskPredicates.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +import com.google.common.base.Predicate; + +public class TaskPredicates { + + private TaskPredicates() { + } + + public static Predicate<AbstractTaskCategory> containsTask(final AbstractTask task) { + return new Predicate<AbstractTaskCategory>() { + @Override + public boolean apply(AbstractTaskCategory category) { + return category.contains(task.getHandleIdentifier()); + } + }; + } + + public static Predicate<ITask> hasTaskKey(final String taskKey) { + return new Predicate<ITask>() { + @Override + public boolean apply(ITask task) { + return taskKey.equals(task.getTaskKey()); + } + }; + } + + public static Predicate<ITask> isTaskForConnector(final String kind) { + return new Predicate<ITask>() { + @Override + public boolean apply(ITask task) { + return kind.equals(task.getConnectorKind()); + } + }; + } + + public static Predicate<IRepositoryQuery> isQueryForRepository(final TaskRepository repository) { + return new Predicate<IRepositoryQuery>() { + @Override + public boolean apply(IRepositoryQuery query) { + return repository.getConnectorKind().equals(query.getConnectorKind()) + && repository.getRepositoryUrl().equals(query.getRepositoryUrl()); + } + }; + } + + public static Predicate<IRepositoryQuery> isQueryForConnector(final String kind) { + return new Predicate<IRepositoryQuery>() { + @Override + public boolean apply(IRepositoryQuery query) { + return kind.equals(query.getConnectorKind()); + } + }; + } + + public static Predicate<RepositoryQuery> isSynchronizing() { + return new Predicate<RepositoryQuery>() { + @Override + public boolean apply(RepositoryQuery query) { + return query.isSynchronizing(); + } + }; + } + + public static Predicate<AbstractTask> isTaskSynchronizing() { + return new Predicate<AbstractTask>() { + @Override + public boolean apply(AbstractTask task) { + return task.isSynchronizing(); + } + }; + } +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TasksState.java b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TasksState.java new file mode 100644 index 000000000..8294f53df --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/TasksState.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2015 Tasktop Technologies. + * 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.ui.migrator; + +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.TaskActivityManager; +import org.eclipse.mylyn.internal.tasks.core.TaskJobFactory; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.context.AbstractTaskContextStore; + +public class TasksState { + + private final TaskActivityManager taskActivityManager; + + private final TaskDataManager taskDataManager; + + private final RepositoryModel repositoryModel; + + private final TaskList taskList; + + private final AbstractTaskContextStore contextStore; + + private final TaskJobFactory taskJobFactory; + + private final TaskRepositoryManager repositoryManager; + + public TasksState(TaskList taskList, TaskDataManager taskDataManager, TaskRepositoryManager repositoryManager, + RepositoryModel repositoryModel, AbstractTaskContextStore contextStore, + TaskActivityManager taskActivityManager, TaskJobFactory taskJobFactory) { + this.taskList = taskList; + this.taskDataManager = taskDataManager; + this.repositoryManager = repositoryManager; + this.repositoryModel = repositoryModel; + this.contextStore = contextStore; + this.taskActivityManager = taskActivityManager; + this.taskJobFactory = taskJobFactory; + } + + public TaskActivityManager getTaskActivityManager() { + return taskActivityManager; + } + + public TaskDataManager getTaskDataManager() { + return taskDataManager; + } + + public RepositoryModel getRepositoryModel() { + return repositoryModel; + } + + public TaskList getTaskList() { + return taskList; + } + + public AbstractTaskContextStore getContextStore() { + return contextStore; + } + + public TaskJobFactory getTaskJobFactory() { + return taskJobFactory; + } + + public TaskRepositoryManager getRepositoryManager() { + return repositoryManager; + } + +} diff --git a/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/messages.properties b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/messages.properties new file mode 100644 index 000000000..543f4ad56 --- /dev/null +++ b/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/migrator/messages.properties @@ -0,0 +1,46 @@ +CompleteConnectorMigrationWizard_Complete_Connector_Migration=Complete Connector Migration +CompleteConnectorMigrationWizard_Complete_Migration=Complete Migration +CompleteConnectorMigrationWizard_first_page_message=Migration will remove your old queries. Please ensure you have created the new \ +queries you want. Your old and new queries are shown below and you can edit them by double-clicking. +CompleteConnectorMigrationWizard_Have_You_Recreated_Your_Queries=Have You Recreated Your Queries? +CompleteConnectorMigrationWizard_Migrate_Queries=Migrate Queries +CompleteConnectorMigrationWizard_Queries_Using_New_Connectors=Queries Using New Connectors +CompleteConnectorMigrationWizard_Queries_Using_Old_Connectors=Queries Using Old Connectors +CompleteConnectorMigrationWizard_second_page_message=Clicking finish will migrate your tasks and private data. This may take a while. +CompleteConnectorMigrationWizard_second_page_text=When you click finish, your context, scheduled dates, private notes and other data \ +will be migrated to the new connectors. Any tasks \ +in your task list that are not included in the new queries you created will be downloaded using the new connectors. The old tasks, \ +queries, and repositories will be deleted.\n\ +\n\ +This may take a while. You should not use the task list or task editor while this is happening. You will be prompted when migration \ +is complete.\n\ +\n\ +You will be able to undo the migration by selecting "Restore Tasks from History" in the Task List view menu and choosing the \ +connector-migration-*.zip file stored in <workspace>/.metadata/.mylyn/backup. This will restore your task list and repositories to \ +the state they were in before the migration, but any data stored by 3rd party plugins for Mylyn may be lost. +ConnectorMigrationWizard_Connector_Migration=Connector Migration +ConnectorMigrationWizard_End_of_Connector_Support=End of Connector Support +ConnectorMigrationWizard_Message=Support is ending for some connectors, but replacement connectors are installed. This wizard \ +will help you migrate your configuration and data to the new connectors. +ConnectorMigrationWizard_Body=\{0}\n\ +\n\ +Your tasks, contexts, private notes, categories, scheduled dates, and private due dates \ +will be automatically migrated for all tasks. Incoming state will be migrated but all unsubmitted outgoing changes will be lost. \ +You may want to cancel this wizard and submit your outgoing changes before continuing with migration.\n\ +\n\ +This wizard will migrate your task repositories but you will need to manually migrate your queries before your tasks and private data \ +will be migrated. After finishing this wizard, please recreate your queries using the new connectors and then click the link in the \ +task list to complete the migration. The new connectors may not support the same queries as the old ones, \ +for example, you might need to create queries that use server-side searches or favorite filters that you create in the repository's \ +web UI. +ConnectorMigrationWizard_Select_Connectors=Select Connectors +ConnectorMigrationWizard_Select_the_connectors_to_migrate=Select the connectors to migrate. Your task list and repositories will be \ +backed up before migration; you can undo the migration \ +by selecting "Restore Tasks from History" in the Task List view menu and choosing the connector-migration-*.zip file stored \ +in <workspace>/.metadata/.mylyn/backup. +ConnectorMigrationWizard_validation_failed=Could not validate the connection to the following repositories. Additional configuration may \ +be required. Please edit the repository settings via the Task Repositories view.\n\n{0} +ConnectorMigrator_complete_migration_prompt_message=Support is ending for some of your connectors, but replacement connectors are \ +installed. <a href="{0}">Click here</a> for more information or to migrate to the new connectors. +ConnectorMigrator_complete_migration_prompt_title=Connector migration is not finished. Manually recreate your queries using the new \ +connectors and then click <a href="{0}">complete migration</a> to migrate your contexts, scheduled dates, private notes and other data. |