| author | Sebastian Schmidt | 2012-05-21 12:43:37 (EDT) |
|---|---|---|
| committer | Sebastian Schmidt | 2012-05-21 12:43:37 (EDT) |
| commit | e8a53673bf36d66d72bbc9498d6ca37feff37c68 (patch) (side-by-side diff) | |
| tree | d324b93c22bd8c95840a2b92dd172355ee22e66a | |
| parent | 75f8056d89bcc5ae826bae5ca149f342c712cfa3 (diff) | |
| download | org.eclipse.mylyn.context-e8a53673bf36d66d72bbc9498d6ca37feff37c68.zip org.eclipse.mylyn.context-e8a53673bf36d66d72bbc9498d6ca37feff37c68.tar.gz org.eclipse.mylyn.context-e8a53673bf36d66d72bbc9498d6ca37feff37c68.tar.bz2 | |
allow third party contributions to the contextrefs/changes/52/6052/4
This change enables third party plugins to contribute content
to Mylyn context. Third party plugins can register themselves
as IContextContributor and will be asked for additional data
before a save operation on a Mylyn context.
Additionally, they are allowed to receive these additional
data at any time a context is active.
The IContextContributor registration should be enabled as an
extension point in later versions.
Bug: 378399
Change-Id: I704ede7baf022b7718cdb5e58a68405e14b985da
10 files changed, 215 insertions, 3 deletions
diff --git a/org.eclipse.mylyn.context.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.context.core/META-INF/MANIFEST.MF index 78ac595..2b90413 100644 --- a/org.eclipse.mylyn.context.core/META-INF/MANIFEST.MF +++ b/org.eclipse.mylyn.context.core/META-INF/MANIFEST.MF @@ -8,6 +8,7 @@ Bundle-Localization: plugin Require-Bundle: org.eclipse.core.runtime, org.eclipse.mylyn.commons.core;bundle-version="[3.8.0,4.0.0)", org.eclipse.mylyn.monitor.core;bundle-version="[3.8.0,4.0.0)" +Import-Package: org.apache.commons.io;version="[1.4.0,3.0.0)" Bundle-ActivationPolicy: lazy Bundle-Vendor: %Bundle-Vendor Export-Package: org.eclipse.mylyn.context.core, diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/ContextCore.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/ContextCore.java index 5a17594..2ae4613 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/ContextCore.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/ContextCore.java @@ -11,6 +11,7 @@ package org.eclipse.mylyn.context.core; +import java.util.List; import java.util.Set; import org.eclipse.mylyn.internal.context.core.ContextCorePlugin; @@ -57,4 +58,10 @@ public final class ContextCore { return ContextCorePlugin.getContextStore(); } + /** + * @since 3.9 + */ + public static List<IContextContributor> getContextContributor() { + return ContextCorePlugin.getDefault().getContextContributor(); + } } diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IContextContributor.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IContextContributor.java new file mode 100644 index 0000000..34ed795 --- a/dev/null +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IContextContributor.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2012 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sebastian Schmidt - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.context.core; + +import java.io.InputStream; + +/** + * A ContextContributor may be used to put additional data to Mylyn context. As the ContextContributor is in charge of + * serialization and deserialization of provided data, the data type is not limited. + * + * @since 3.8 + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IContextContributor { + + /** + * Provides data which should be added to the given context. + * + * @param context + * context that is going to be saved + * @return an InputStream with context related data or null + */ + public InputStream getDataAsStream(IInteractionContext context); + + /** + * @return an unique identifier for this ContextContributor + */ + public String getIdentifier(); +} diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IInteractionContextManager.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IInteractionContextManager.java index 574bae3..6ff14b2 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IInteractionContextManager.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/context/core/IInteractionContextManager.java @@ -11,6 +11,7 @@ package org.eclipse.mylyn.context.core; +import java.io.InputStream; import java.util.Collection; import java.util.Set; @@ -72,4 +73,9 @@ public interface IInteractionContextManager { * NOTE: If pausing ensure to restore to original state. */ public void setContextCapturePaused(boolean paused); + + /** + * @since 3.9 + */ + public InputStream getAdditionalContextData(IInteractionContext context, String identifier); }
\ No newline at end of file diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/ContextCorePlugin.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/ContextCorePlugin.java index e294991..fe7ecf8 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/ContextCorePlugin.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/ContextCorePlugin.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import org.eclipse.core.runtime.IConfigurationElement; @@ -33,6 +34,7 @@ import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.context.core.AbstractContextStructureBridge; import org.eclipse.mylyn.context.core.ContextCore; +import org.eclipse.mylyn.context.core.IContextContributor; import org.eclipse.mylyn.context.core.IInteractionContextScaling; import org.osgi.framework.BundleContext; @@ -50,6 +52,8 @@ public class ContextCorePlugin extends Plugin { private final Map<String, Set<String>> childContentTypeMap = new ConcurrentHashMap<String, Set<String>>(); + private final List<IContextContributor> contextContributor = new CopyOnWriteArrayList<IContextContributor>(); + // specifies that one content type should shadow another // the <value> content type shadows the <key> content typee private final Map<String, String> contentTypeToShadowMap = new ConcurrentHashMap<String, String>(); @@ -210,6 +214,19 @@ public class ContextCorePlugin extends Plugin { return bridges; } + public List<IContextContributor> getContextContributor() { + return contextContributor; + } + + // TODO: add extension point to register context provider + public void addContextContributor(IContextContributor contributor) { + contextContributor.add(contributor); + } + + public void removeContextContributor(IContextContributor contributor) { + contextContributor.remove(contributor); + } + /** * Finds the shadowed content for the passed in base content * diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextExternalizer.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextExternalizer.java index 134bca6..c63b338 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextExternalizer.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextExternalizer.java @@ -14,17 +14,24 @@ package org.eclipse.mylyn.internal.context.core; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Enumeration; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import org.apache.commons.io.IOUtils; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.context.core.IContextContributor; import org.eclipse.mylyn.context.core.IInteractionContext; import org.eclipse.mylyn.context.core.IInteractionContextScaling; @@ -144,6 +151,62 @@ public class InteractionContextExternalizer { writer.writeContextToStream(context); outputStream.flush(); outputStream.closeEntry(); + + addAdditionalInformation(context, outputStream); + } + + private void addAdditionalInformation(final IInteractionContext context, final ZipOutputStream outputStream) + throws IOException { + for (final IContextContributor contributor : getContextContributor()) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, + "Context contribution failed: " //$NON-NLS-1$ + + contributor.getClass(), e)); + } + + public void run() throws Exception { + InputStream additionalContextInformation = contributor.getDataAsStream(context); + if (additionalContextInformation != null) { + String encoded = URLEncoder.encode(contributor.getIdentifier(), + InteractionContextManager.CONTEXT_FILENAME_ENCODING); + ZipEntry zipEntry = new ZipEntry(encoded); + outputStream.putNextEntry(zipEntry); + IOUtils.copy(additionalContextInformation, outputStream); + outputStream.flush(); + outputStream.closeEntry(); + } + } + }); + } + } + + public InputStream getAdditionalInformation(File file, String contributorIdentifier) throws IOException { + if (!file.exists()) { + return null; + } + ZipFile zipFile = new ZipFile(file); + ZipEntry entry = findFileInZip(zipFile, contributorIdentifier); + if (entry == null) { + return null; + } + + return zipFile.getInputStream(entry); + } + + private ZipEntry findFileInZip(ZipFile zipFile, String identifier) throws UnsupportedEncodingException { + String encoded = URLEncoder.encode(identifier, InteractionContextManager.CONTEXT_FILENAME_ENCODING); + for (Enumeration<?> e = zipFile.entries(); e.hasMoreElements();) { + ZipEntry entry = (ZipEntry) e.nextElement(); + if (entry.getName().equals(encoded)) { + return entry; + } + } + return null; + } + + private List<IContextContributor> getContextContributor() { + return ContextCorePlugin.getDefault().getContextContributor(); } public IInteractionContext readContextFromXml(String handleIdentifier, File fromFile, diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextManager.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextManager.java index a719733..1505bab 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextManager.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/InteractionContextManager.java @@ -12,6 +12,8 @@ package org.eclipse.mylyn.internal.context.core; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -39,12 +41,12 @@ import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.context.core.AbstractContextListener; import org.eclipse.mylyn.context.core.AbstractContextStructureBridge; import org.eclipse.mylyn.context.core.ContextChangeEvent; +import org.eclipse.mylyn.context.core.ContextChangeEvent.ContextChangeKind; import org.eclipse.mylyn.context.core.ContextCore; import org.eclipse.mylyn.context.core.IInteractionContext; import org.eclipse.mylyn.context.core.IInteractionContextManager; import org.eclipse.mylyn.context.core.IInteractionElement; import org.eclipse.mylyn.context.core.IInteractionRelation; -import org.eclipse.mylyn.context.core.ContextChangeEvent.ContextChangeKind; import org.eclipse.mylyn.monitor.core.InteractionEvent; import org.eclipse.mylyn.monitor.core.InteractionEvent.Kind; @@ -420,6 +422,17 @@ public class InteractionContextManager implements IInteractionContextManager { } } + public InputStream getAdditionalContextData(IInteractionContext context, String contributorIdentifier) { + try { + return contextStore.getAdditionalContextInformation(context, contributorIdentifier); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, + "Searching for additional context data failed" //$NON-NLS-1$ + , e)); + } + return null; + } + public void deactivateContext(String handleIdentifier) { try { System.setProperty(InteractionContextManager.PROPERTY_CONTEXT_ACTIVE, Boolean.FALSE.toString()); diff --git a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/LocalContextStore.java b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/LocalContextStore.java index 4801769..e4c7bfa 100644 --- a/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/LocalContextStore.java +++ b/org.eclipse.mylyn.context.core/src/org/eclipse/mylyn/internal/context/core/LocalContextStore.java @@ -13,6 +13,7 @@ package org.eclipse.mylyn.internal.context.core; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; @@ -82,6 +83,12 @@ public class LocalContextStore implements IContextStore { return loadContext(handleIdentifier, getFileForContext(handleIdentifier), commonContextScaling); } + public InputStream getAdditionalContextInformation(IInteractionContext context, String identifier) + throws IOException { + File fileForContext = getFileForContext(context.getHandleIdentifier()); + return externalizer.getAdditionalInformation(fileForContext, identifier); + } + public IInteractionContext importContext(String handleIdentifier, File fromFile) throws CoreException { InteractionContext context; String handleToImportFrom; diff --git a/org.eclipse.mylyn.context.tests/META-INF/MANIFEST.MF b/org.eclipse.mylyn.context.tests/META-INF/MANIFEST.MF index 3ef8ce2..0cae2cb 100644 --- a/org.eclipse.mylyn.context.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.mylyn.context.tests/META-INF/MANIFEST.MF @@ -17,7 +17,10 @@ Require-Bundle: org.junit, org.eclipse.mylyn.context.sdk.util, org.eclipse.mylyn.context.ui, org.eclipse.mylyn.monitor.core, - org.eclipse.mylyn.monitor.ui + org.eclipse.mylyn.monitor.ui, + org.mockito;bundle-version="[1.8.4,2.0.0)", + org.objenesis;bundle-version="[1.0.0,2.0.0)", + org.hamcrest;bundle-version="[1.0.0,2.0.0)" Export-Package: org.eclipse.mylyn.context.tests;x-internal:=true, org.eclipse.mylyn.context.tests.support;x-internal:=true, org.eclipse.mylyn.context.tests.support.search;x-internal:=true diff --git a/org.eclipse.mylyn.context.tests/src/org/eclipse/mylyn/context/tests/ContextExternalizerTest.java b/org.eclipse.mylyn.context.tests/src/org/eclipse/mylyn/context/tests/ContextExternalizerTest.java index 4d6edfd..e5eb933 100644 --- a/org.eclipse.mylyn.context.tests/src/org/eclipse/mylyn/context/tests/ContextExternalizerTest.java +++ b/org.eclipse.mylyn.context.tests/src/org/eclipse/mylyn/context/tests/ContextExternalizerTest.java @@ -11,18 +11,25 @@ package org.eclipse.mylyn.context.tests; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.Scanner; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.eclipse.mylyn.commons.sdk.util.CommonTestUtil; import org.eclipse.mylyn.context.core.ContextCore; +import org.eclipse.mylyn.context.core.IContextContributor; import org.eclipse.mylyn.context.core.IInteractionContext; import org.eclipse.mylyn.context.core.IInteractionContextScaling; import org.eclipse.mylyn.context.core.IInteractionElement; import org.eclipse.mylyn.context.core.IInteractionRelation; -import org.eclipse.mylyn.context.sdk.util.AbstractContextTest; import org.eclipse.mylyn.context.tests.support.DomContextReader; import org.eclipse.mylyn.context.tests.support.DomContextWriter; import org.eclipse.mylyn.internal.context.core.ContextCorePlugin; @@ -30,6 +37,7 @@ import org.eclipse.mylyn.internal.context.core.InteractionContext; import org.eclipse.mylyn.internal.context.core.InteractionContextExternalizer; import org.eclipse.mylyn.internal.context.core.InteractionContextManager; import org.eclipse.mylyn.internal.context.core.SaxContextReader; +import org.eclipse.mylyn.monitor.core.InteractionEvent; /** * @author Mik Kersten @@ -43,6 +51,8 @@ public class ContextExternalizerTest extends AbstractContextTest { private IInteractionContextScaling scaling; + private File contextFile; + @Override protected void setUp() throws Exception { super.setUp(); @@ -53,6 +63,10 @@ public class ContextExternalizerTest extends AbstractContextTest { @Override protected void tearDown() throws Exception { + if (contextFile != null && contextFile.exists()) { + contextFile.delete(); + } + super.tearDown(); } @@ -297,4 +311,46 @@ public class ContextExternalizerTest extends AbstractContextTest { assertNull(context); } + public void testAddContextContributor() throws Exception { + InteractionContextExternalizer externalizer = new InteractionContextExternalizer(); + ContextCorePlugin contextCorePlugin = ContextCorePlugin.getDefault(); + IContextContributor contributor = mock(IContextContributor.class); + when(contributor.getDataAsStream(context)).thenReturn(null); + + contextCorePlugin.addContextContributor(contributor); + assertEquals(1, contextCorePlugin.getContextContributor().size()); + assertEquals(contributor, contextCorePlugin.getContextContributor().get(0)); + + externalizer.writeContext(context, mock(ZipOutputStream.class)); + verify(contributor).getDataAsStream(context); + + contextCorePlugin.removeContextContributor(contributor); + assertEquals(0, contextCorePlugin.getContextContributor().size()); + } + + public void testWriteAdditionalContextData() throws Exception { + InteractionContextExternalizer externalizer = new InteractionContextExternalizer(); + IContextContributor contributor = mock(IContextContributor.class); + InteractionEvent event = mockNavigation("InteractionEvent"); + context.parseEvent(event); + + String testContributorId = "myContributor"; + String testData = "important context information"; + InputStream testStream = new ByteArrayInputStream(testData.getBytes()); + when(contributor.getIdentifier()).thenReturn(testContributorId); + when(contributor.getDataAsStream(context)).thenReturn(testStream); + ContextCorePlugin.getDefault().addContextContributor(contributor); + + contextFile = ContextCorePlugin.getContextStore().getFileForContext(context.getHandleIdentifier()); + + externalizer.writeContextToXml(context, contextFile); + InputStream resultStream = externalizer.getAdditionalInformation(contextFile, testContributorId); + assertNotNull(resultStream); + assertNull(externalizer.getAdditionalInformation(contextFile, "nonExistingContributor")); + assertEquals(testData, new Scanner(resultStream).useDelimiter("\\A").next()); + + resultStream = ContextCore.getContextManager().getAdditionalContextData(context, testContributorId); + assertNotNull(resultStream); + assertNull(externalizer.getAdditionalInformation(contextFile, "nonExistingContributor")); + } } |

