Bug 339951 - [sfs] add ability to rollback SFS cache update
diff --git a/bundles/org.eclipse.core.resources.semantic/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.resources.semantic/META-INF/MANIFEST.MF index 2d0a595..d169b1e 100644 --- a/bundles/org.eclipse.core.resources.semantic/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.core.resources.semantic/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.core.resources.semantic;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-Activator: org.eclipse.core.internal.resources.semantic.SemanticResourcesPlugin Require-Bundle: org.eclipse.core.runtime;bundle-version="3.4.0", org.eclipse.core.resources;bundle-version="3.4.2",
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/CachingOutputStream.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/CachingOutputStream.java index 265ba11..fac844b 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/CachingOutputStream.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/CachingOutputStream.java
@@ -51,32 +51,48 @@ closed = true; + try { + stream = this.fileHandle.closeAndGetContents(); + + long appendPosition = this.fileHandle.getAppendPosition(); + skipUntilAppendPosition(stream, appendPosition); + + this.callback.beforeCacheUpdate(stream, timestamp, this.appendMode); + } finally { + Util.safeClose(stream); + stream = null; + } + this.cacheService.addFromTempHandle(this.fileHandle); IPath path = this.fileHandle.getKey(); stream = this.cacheService.getContent(path); long appendPosition = this.fileHandle.getAppendPosition(); - long skipped = 0l; - if (appendPosition > 0) { - skipped = stream.skip(appendPosition); - } - - if (skipped < appendPosition) { - throw new IOException(MessageFormat.format(Messages.CachingOutputStream_CouldNotSkip_XMSG, appendPosition, skipped)); - } + skipUntilAppendPosition(stream, appendPosition); this.callback.cacheUpdated(stream, timestamp, this.appendMode); } catch (CoreException e) { - // $JL-EXC$ ignore - throw new IOException(e.getMessage()); + throw new IOException(e.getMessage(), e); } finally { Util.safeClose(stream); } } + private void skipUntilAppendPosition(InputStream stream, long appendPosition) throws IOException { + long skipped = 0l; + + if (appendPosition > 0) { + skipped = stream.skip(appendPosition); + } + + if (skipped < appendPosition) { + throw new IOException(MessageFormat.format(Messages.CachingOutputStream_CouldNotSkip_XMSG, appendPosition, skipped)); + } + } + @Override public void flush() throws IOException { this.fileHandle.flush();
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/ITemporaryContentHandle.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/ITemporaryContentHandle.java index 7731fa9..9e1f3b3 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/ITemporaryContentHandle.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/ITemporaryContentHandle.java
@@ -108,4 +108,12 @@ * upon failure */ public void setContents(InputStream input, IProgressMonitor monitor) throws CoreException; + + /** + * Used by the cache service when content is added to the cache + * + * @throws CoreException + * upon failure + */ + public InputStream closeAndGetContents() throws CoreException; }
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/Messages.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/Messages.java index 34ba091..d376558 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/Messages.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/Messages.java
@@ -29,6 +29,7 @@ public static String FileHandleFactory_FileHandleFactory_FileHandleFactory_SaveError_XMSG; public static String FileHandleFactory_FileHandleFactory_LoadError_XMSG; public static String FileHandleFactory_TempFileNotRenamed_XMSG; + public static String TemporaryFileHandle_FailureOpeningCacheTempFile_XMSG; public static String TemporaryFileHandle_OsCloseErrorOnCommit_XMSG; public static String TemporaryFileHandle_TimstampSetOnCommit_XMSG; }
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryFileHandle.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryFileHandle.java index 5f020f1..4978eaf 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryFileHandle.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryFileHandle.java
@@ -12,6 +12,8 @@ package org.eclipse.core.internal.resources.semantic.cacheservice; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -77,7 +79,24 @@ } public void commit() throws CoreException { + if (!this.appendMode) { + this.factory.doRename(this.getFile(), this.cacheFile); + } + } + public InputStream closeAndGetContents() throws CoreException { + close(); + + try { + return new FileInputStream(getFile()); + } catch (FileNotFoundException e) { + throw new SemanticResourceException(SemanticResourceStatusCode.FILECACHE_CACHEFILE_READ_TEMPFILE_FAILED, new Path( + this.file.getAbsolutePath()), MessageFormat.format(Messages.TemporaryFileHandle_FailureOpeningCacheTempFile_XMSG, + this.file.getAbsolutePath()), e); + } + } + + private void close() throws SemanticResourceException { try { this.outputStream.flush(); this.outputStream.close(); @@ -91,13 +110,9 @@ this.factory.tryDelete(this.file); } - throw new SemanticResourceException(SemanticResourceStatusCode.FILECACHE_ERROR_WRITING_CONTENT, new Path(this.file - .getAbsolutePath()), MessageFormat.format(Messages.TemporaryFileHandle_OsCloseErrorOnCommit_XMSG, this.file - .getAbsolutePath()), e); - } - - if (!this.appendMode) { - this.factory.doRename(this.getFile(), this.cacheFile); + throw new SemanticResourceException(SemanticResourceStatusCode.FILECACHE_ERROR_WRITING_CONTENT, new Path( + this.file.getAbsolutePath()), MessageFormat.format(Messages.TemporaryFileHandle_OsCloseErrorOnCommit_XMSG, + this.file.getAbsolutePath()), e); } }
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryMemoryHandle.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryMemoryHandle.java index 072e061..66e725a 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryMemoryHandle.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/TemporaryMemoryHandle.java
@@ -11,6 +11,7 @@ *******************************************************************************/ package org.eclipse.core.internal.resources.semantic.cacheservice; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -37,13 +38,16 @@ */ public void commit() throws CoreException { CachedMemoryHandle handle = MemoryCache.getInstance().getOrCreateMemoryStore(this.path.toString()); - byte[] content; - - Util.safeClose(this.bos); - content = this.bos.toByteArray(); + byte[] content = this.bos.toByteArray(); handle.setContents(content, this.appendMode); } + public InputStream closeAndGetContents() { + Util.safeClose(this.bos); + byte[] content = this.bos.toByteArray(); + return new ByteArrayInputStream(content); + } + public IPath getKey() { return this.path; }
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/messages.properties b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/messages.properties index 89b99eb..4a59518 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/messages.properties +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/internal/resources/semantic/cacheservice/messages.properties
@@ -14,5 +14,6 @@ FileHandleFactory_FileHandleFactory_FileHandleFactory_SaveError_XMSG=Error writing persistent data for file cache management FileHandleFactory_FileHandleFactory_LoadError_XMSG=Error reading persistent data for file cache management FileHandleFactory_TempFileNotRenamed_XMSG=Temporary File could not be renamed to {0} +TemporaryFileHandle_FailureOpeningCacheTempFile_XMSG=Temporary file {0} could not be opened for read TemporaryFileHandle_OsCloseErrorOnCommit_XMSG=OutputStream could not be closed while committing temporary file {0} TemporaryFileHandle_TimstampSetOnCommit_XMSG=Modification time could not be set on cache file while committing temporary file {0}
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/SemanticResourceStatusCode.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/SemanticResourceStatusCode.java index 9412d48..b49e5e6 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/SemanticResourceStatusCode.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/SemanticResourceStatusCode.java
@@ -220,7 +220,14 @@ * * @since 0.4 */ - FEDERATION_INVALID_ROOT_NODE_POSITION(167); + FEDERATION_INVALID_ROOT_NODE_POSITION(167), + + /** + * Error reading temporary file from cache + * + * @since 0.5 + */ + FILECACHE_CACHEFILE_READ_TEMPFILE_FAILED(168); /** * The plug-in ID
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/CachingContentProvider.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/CachingContentProvider.java index 9cd04d3..60d21be 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/CachingContentProvider.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/CachingContentProvider.java
@@ -55,8 +55,6 @@ * <p> * This class is intended to be subclassed. * - * @since 4.0 - * */ public abstract class CachingContentProvider extends ContentProvider { @@ -172,6 +170,10 @@ public void cacheUpdated(InputStream newContent, long timestamp, boolean append) { onCacheUpdate(childStore, newContent, timestamp, append, monitor); } + + public void beforeCacheUpdate(InputStream newContent, long timestamp, boolean append) throws CoreException { + CachingContentProvider.this.beforeCacheUpdate(childStore, newContent, timestamp, append, monitor); + } }; return cacheService.wrapOutputStream(path, appendMode, callback, monitor); } @@ -421,6 +423,36 @@ } } + /** + * Notification before cache content should be updated. + * <p> + * This method will be called when the output stream is closed. Content + * providers may override this method to update the remote repository. + * <p> + * The cache will not be changed if this method throws an exception. + * + * @param childStore + * the semantic file store + * @param newContent + * the new cache content + * @param timestamp + * the timestamp of the change + * @param append + * <code>true</code> to indicate that the cache was updated in + * append mode; in this case, only the appended data will be + * provided as new content + * @param monitor + * may be null + * @throws CoreException + * if update of remote repository has failed + * + * @since 0.5 + */ + public void beforeCacheUpdate(ISemanticFileStore childStore, InputStream newContent, long timestamp, boolean append, + IProgressMonitor monitor) throws CoreException { + // do nothing as default + } + public long getResourceTimestamp(ISemanticFileStore semanticFileStore, IProgressMonitor monitor) throws CoreException { if (!semanticFileStore.isExists()) {
diff --git a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/ICacheUpdateCallback.java b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/ICacheUpdateCallback.java index bb4061d..af7af45 100644 --- a/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/ICacheUpdateCallback.java +++ b/bundles/org.eclipse.core.resources.semantic/src/org/eclipse/core/resources/semantic/spi/ICacheUpdateCallback.java
@@ -33,4 +33,20 @@ * @throws CoreException */ public void cacheUpdated(InputStream newContent, long cacheTimestamp, boolean append) throws CoreException; + + /** + * Notifies that the cache is about to be updated + * + * @param newContent + * the content to be added to the cache + * @param cacheTimestamp + * the cache timestamp + * @param append + * if <code>true</code>, the content only represents the appended + * portion of the cache + * @throws CoreException + * + * @since 0.5 + */ + public void beforeCacheUpdate(InputStream newContent, long cacheTimestamp, boolean append) throws CoreException; }
diff --git a/bundles/org.eclipse.ui.resources.semantic/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.resources.semantic/META-INF/MANIFEST.MF index 1e9b285..70b3f3c 100644 --- a/bundles/org.eclipse.ui.resources.semantic/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.resources.semantic/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.ui.resources.semantic;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-Activator: org.eclipse.core.internal.resources.semantic.ui.SemanticResourcesUIPlugin Require-Bundle: org.eclipse.ui;bundle-version="3.4.2", org.eclipse.core.runtime;bundle-version="3.4.0",
diff --git a/examples/org.eclipse.core.resources.semantic.examples/META-INF/MANIFEST.MF b/examples/org.eclipse.core.resources.semantic.examples/META-INF/MANIFEST.MF index af4b437..8ef8f28 100644 --- a/examples/org.eclipse.core.resources.semantic.examples/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.core.resources.semantic.examples/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Examples how to use Semantic File System Bundle-SymbolicName: org.eclipse.core.resources.semantic.examples;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-Activator: org.eclipse.core.resources.semantic.examples.remote.SemanticResourcesPluginExamplesCore Require-Bundle: org.eclipse.ui;bundle-version="3.4.2", org.eclipse.core.runtime;bundle-version="3.4.0",
diff --git a/examples/org.eclipse.ui.resources.semantic.examples/META-INF/MANIFEST.MF b/examples/org.eclipse.ui.resources.semantic.examples/META-INF/MANIFEST.MF index 4f1ac7b..59dc294 100644 --- a/examples/org.eclipse.ui.resources.semantic.examples/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.ui.resources.semantic.examples/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.ui.resources.semantic.examples;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-Activator: org.eclipse.core.resources.semantic.examples.SemanticResourcesPluginExamples Require-Bundle: org.eclipse.ui;bundle-version="3.4.2", org.eclipse.core.runtime;bundle-version="3.4.0",
diff --git a/tests/org.eclipse.core.resources.semantic.test/META-INF/MANIFEST.MF b/tests/org.eclipse.core.resources.semantic.test/META-INF/MANIFEST.MF index c563b16..6fdbe5d 100644 --- a/tests/org.eclipse.core.resources.semantic.test/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.core.resources.semantic.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Unit Tests for the Semantic File System Bundle-SymbolicName: org.eclipse.core.resources.semantic.test;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-ClassPath: . Bundle-RequiredExecutionEnvironment: J2SE-1.5 Require-Bundle: org.junit4;bundle-version="4.3.1",
diff --git a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestCacheService.java b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestCacheService.java index f25c9f5..3d5e24c 100644 --- a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestCacheService.java +++ b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestCacheService.java
@@ -78,6 +78,18 @@ removeFromCache(service, path2); manipulateTimestamp(service, path); + + String content5 = "test5"; + writeToCacheAndUseBeforeCacheUpdate(service, path, content5); + + readFromCache(service, path, content5); + + // this call must leave cache content unchanged + String content6 = "test6"; + writeToCacheAndThrowExceptionInBeforeCacheUpdate(service, path, content6); + + // we must still read content5 from cache + readFromCache(service, path, content5); } /** @@ -429,6 +441,10 @@ } } + + public void beforeCacheUpdate(InputStream newContent, long cacheTimestamp, boolean append) { + // do nothing + } }; OutputStream os = service.wrapOutputStream(path, false, callback, null); @@ -446,6 +462,64 @@ } + public void writeToCacheAndUseBeforeCacheUpdate(ICacheService service, IPath path, String content) throws CoreException { + + final ByteArrayOutputStream remoteOs = new ByteArrayOutputStream(); + + ICacheUpdateCallback callback = new ICacheUpdateCallback() { + + public void cacheUpdated(InputStream newContent, long cacheTimestamp, boolean append) { + // do nothing + } + + public void beforeCacheUpdate(InputStream newContent, long cacheTimestamp, boolean append) throws CoreException { + Util.transferStreams(newContent, remoteOs, null); + } + }; + + OutputStream os = service.wrapOutputStream(path, false, callback, null); + + try { + os.write(content.getBytes()); + + os.close(); + + compareByteArrayWithContent(content, remoteOs.toByteArray()); + } catch (IOException e) { + // $JL-EXC$ + throw new CoreException(new Status(IStatus.ERROR, "test", 0, content, e)); + } + + } + + public void writeToCacheAndThrowExceptionInBeforeCacheUpdate(ICacheService service, IPath path, String content) throws CoreException { + + ICacheUpdateCallback callback = new ICacheUpdateCallback() { + + public void cacheUpdated(InputStream newContent, long cacheTimestamp, boolean append) { + // do nothing + } + + public void beforeCacheUpdate(InputStream newContent, long cacheTimestamp, boolean append) throws CoreException { + throw new CoreException(new Status(IStatus.ERROR, "test", "test")); + } + }; + + OutputStream os = service.wrapOutputStream(path, false, callback, null); + + try { + os.write(content.getBytes()); + + os.close(); + + Assert.assertTrue("os.close() must fail", false); + } catch (IOException e) { + // ignore and continue + String msg = e.getMessage(); + msg.getClass(); + } + } + /** * @param service * @param path
diff --git a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider.java b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider.java index 2781ded..93a2a13 100644 --- a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider.java +++ b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider.java
@@ -281,6 +281,161 @@ } + @Test + public void testBeforeCacheUpdate() throws Exception { + + final IFolder root = this.testProject.getFolder("root"); + final IFolder parent = root.getFolder("Folder1"); + + final IFile file = parent.getFile("File1"); + Assert.assertFalse("File should not exist", file.exists()); + + IWorkspaceRunnable runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFolder sfr = (ISemanticFolder) parent.getAdapter(ISemanticFolder.class); + sfr.addFile("File1", ISemanticFileSystem.NONE, monitor); + Assert.assertEquals("File existence", true, file.exists()); + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + sf.setSessionProperty(CachingTestContentProviderBase.USE_BEFORE_CACHE_UPDATE, "x"); + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + assertContentsEqual(file, "Hello"); + Assert.assertEquals("Wrong remote content", "Hello", new String(this.file1.getContent(), "UTF-8")); + + // setcontent without remote update + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + try { + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + IStatus stat = sf.validateEdit(null); + + Assert.assertTrue("ValidateEdit should have returned OK", stat.isOK()); + + file.setContents(new ByteArrayInputStream("NewString".getBytes("UTF-8")), IResource.KEEP_HISTORY, monitor); + + } catch (UnsupportedEncodingException e) { + // $JL-EXC$ + Assert.fail(e.getMessage()); + } + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + assertContentsEqual(file, "NewString"); + Assert.assertEquals("Wrong remote content", "Hello", new String(this.file1.getContent(), "UTF-8")); + + // setcontent with remote update + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + try { + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + sf.setSessionProperty(CachingTestContentProviderBase.WRITE_THROUGH, ""); + + IStatus stat = sf.validateEdit(null); + + Assert.assertTrue("ValidateEdit should have returned OK", stat.isOK()); + + file.setContents(new ByteArrayInputStream("ThirdString".getBytes("UTF-8")), IResource.KEEP_HISTORY, monitor); + + sf.setSessionProperty(CachingTestContentProviderBase.WRITE_THROUGH, null); + + } catch (UnsupportedEncodingException e) { + // $JL-EXC$ + Assert.fail(e.getMessage()); + } + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + assertContentsEqual(file, "ThirdString"); + Assert.assertEquals("Wrong remote content", "ThirdString", new String(this.file1.getContent(), "UTF-8")); + + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + sf.remove(ISemanticFileSystem.NONE, null); + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + Assert.assertEquals("File existence", false, file.exists()); + + } + + @Test + public void testExceptionInBeforeCacheUpdate() throws Exception { + + final IFolder root = this.testProject.getFolder("root"); + final IFolder parent = root.getFolder("Folder1"); + + final IFile file = parent.getFile("File1"); + Assert.assertFalse("File should not exist", file.exists()); + + IWorkspaceRunnable runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFolder sfr = (ISemanticFolder) parent.getAdapter(ISemanticFolder.class); + sfr.addFile("File1", ISemanticFileSystem.NONE, monitor); + Assert.assertEquals("File existence", true, file.exists()); + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + sf.setSessionProperty(CachingTestContentProviderBase.USE_BEFORE_CACHE_UPDATE, "x"); + sf.setSessionProperty(CachingTestContentProviderBase.EXCEPTION_IN_BEFORE_CACHE_UPDATE, "x"); + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + assertContentsEqual(file, "Hello"); + Assert.assertEquals("Wrong remote content", "Hello", new String(this.file1.getContent(), "UTF-8")); + + // setcontent without remote update + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + try { + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + IStatus stat = sf.validateEdit(null); + + Assert.assertTrue("ValidateEdit should have returned OK", stat.isOK()); + + file.setContents(new ByteArrayInputStream("NewString".getBytes("UTF-8")), IResource.KEEP_HISTORY, monitor); + } catch (UnsupportedEncodingException e) { + // $JL-EXC$ + Assert.fail(e.getMessage()); + } + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + // cached file content must remain unchanged + assertContentsEqual(file, "Hello"); + Assert.assertEquals("Wrong remote content", "Hello", new String(this.file1.getContent(), "UTF-8")); + + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFile sf = (ISemanticFile) file.getAdapter(ISemanticFile.class); + sf.remove(ISemanticFileSystem.NONE, null); + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + Assert.assertEquals("File existence", false, file.exists()); + + } + /** * * @throws Exception @@ -307,8 +462,8 @@ localFile = EFS.getStore(sFile.getAdaptedFile().getLocationURI()).toLocalFile(EFS.NONE, monitor); Assert.assertNotNull("Uncached Local File should not be null", localFile); - Assert.assertTrue("Uncached file should be in SFS cache", localFile.getPath().contains( - "org.eclipse.core.resources.semantic")); + Assert.assertTrue("Uncached file should be in SFS cache", + localFile.getPath().contains("org.eclipse.core.resources.semantic")); } };
diff --git a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider2.java b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider2.java new file mode 100644 index 0000000..4bf894c --- /dev/null +++ b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/TestsCachingProvider2.java
@@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG. + * 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: + * Eduard Bartsch (SAP AG) - initial API and implementation + * Mathias Kinzler (SAP AG) - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources.semantic.test; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.Assert; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IWorkspaceRunnable; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.semantic.ISemanticFile; +import org.eclipse.core.resources.semantic.ISemanticFolder; +import org.eclipse.core.resources.semantic.examples.remote.RemoteFile; +import org.eclipse.core.resources.semantic.examples.remote.RemoteStoreTransient; +import org.eclipse.core.resources.semantic.spi.Util; +import org.eclipse.core.resources.semantic.test.provider.CachingTestContentProvider; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.junit.Test; + +/** + * Caching Content provider tests that use autorefresh + * + */ +public class TestsCachingProvider2 extends TestsContentProviderBase { + + private RemoteFile file1; + + @Override + public void afterMethod() throws Exception { + super.afterMethod(); + this.file1 = null; + } + + @Override + public void beforeMethod() throws Exception { + super.beforeMethod(); + RemoteStoreTransient store = (RemoteStoreTransient) this.testProject.getAdapter(RemoteStoreTransient.class); + this.file1 = (RemoteFile) store.getItemByPath(new Path("Folder1/File1")); + + } + + @Override + public RemoteFile getRemoteFile() { + return this.file1; + } + + /** + * No-argument construcator + */ + public TestsCachingProvider2() { + super(true, "CachingTests", CachingTestContentProvider.class.getName()); + } + + /** + * + * @throws Exception + */ + @Test + public void testAddFileFromRemoteDeleteAndAddAgain() throws Exception { + + final IFolder root = this.testProject.getFolder("root"); + final IFolder parent = root.getFolder("Folder1"); + Assert.assertEquals("Folder existence", false, parent.exists()); + + final IFile file = parent.getFile("File1"); + Assert.assertEquals("File existence", false, file.exists()); + + IWorkspaceRunnable runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFolder sfr = (ISemanticFolder) parent.getAdapter(ISemanticFolder.class); + ISemanticFile sfile = sfr.addFile("File1", TestsCachingProvider2.this.options, monitor); + Assert.assertTrue(sfile.getAdaptedFile().equals(file)); + InputStream is = null; + try { + is = sfile.getAdaptedFile().getContents(); + try { + Assert.assertTrue("Too few bytes available", is.available() > 0); + } catch (IOException e) { + // $JL-EXC$ + Assert.fail(e.getMessage()); + } + } finally { + Util.safeClose(is); + } + } + }; + + // we need to use the project since the root folder is not yet synched + ResourcesPlugin.getWorkspace().run(runnable, this.testProject, 0, new NullProgressMonitor()); + + Assert.assertEquals("File existence", true, file.exists()); + Assert.assertEquals("Folder existence", true, parent.exists()); + + // we keep a file stream so that deletion goes wrong + InputStream is = file.getContents(); + + try { + + runnable = new IWorkspaceRunnable() { + + /** + * @throws CoreException + */ + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFile sFile = (ISemanticFile) file.getAdapter(ISemanticFile.class); + try { + sFile.remove(TestsCachingProvider2.this.options, monitor); + } catch (CoreException e) { + // $JL-EXC$ + Assert.fail("Remove failure"); + } + + } + }; + + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + Assert.assertEquals("File exsistence", false, file.exists()); + + runnable = new IWorkspaceRunnable() { + + public void run(IProgressMonitor monitor) throws CoreException { + ISemanticFolder sfr = (ISemanticFolder) parent.getAdapter(ISemanticFolder.class); + // should work even if cache is deleted logically only + ISemanticFile sfile = sfr.addFile("File1", TestsCachingProvider2.this.options, monitor); + // TODO verify that other operations fail + // (write/read/timestamp) + Assert.assertTrue(sfile.getAdaptedFile().equals(file)); + InputStream is1 = null; + try { + is1 = sfile.getAdaptedFile().getContents(); + try { + Assert.assertTrue("Too few bytes available", is1.available() > 0); + } catch (IOException e) { + // $JL-EXC$ + Assert.fail(e.getMessage()); + } + } finally { + Util.safeClose(is1); + } + } + }; + + try { + // re-adding should not fail even if the input stream is still + // open + ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor()); + + } catch (CoreException e) { + // $JL-EXC$ expected + + Assert.fail("Should not fail"); + } + } finally { + Util.safeClose(is); + } + + Assert.assertEquals("File existence", true, file.exists()); + + } + +}
diff --git a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/provider/CachingTestContentProviderBase.java b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/provider/CachingTestContentProviderBase.java index 8d325ae..aa6fc19 100644 --- a/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/provider/CachingTestContentProviderBase.java +++ b/tests/org.eclipse.core.resources.semantic.test/src/org/eclipse/core/resources/semantic/test/provider/CachingTestContentProviderBase.java
@@ -63,12 +63,96 @@ */ public static final QualifiedName WRITE_THROUGH = new QualifiedName(TestPlugin.PLUGIN_ID, "WriteThrough"); + /** + * If set to any value, this indicates that beforeCacheUpdate method should + * be used + */ + public static final QualifiedName USE_BEFORE_CACHE_UPDATE = new QualifiedName(TestPlugin.PLUGIN_ID, "UseBeforeCacheUpdate"); + private static final QualifiedName LOCKHANDLE = new QualifiedName(TestPlugin.PLUGIN_ID, "LockHandle"); private static final QualifiedName READONLY = new QualifiedName(TestPlugin.PLUGIN_ID, "ReadOnly"); + /** + * If set to any value, this indicates that beforeCacheUpdate method should + * throw an exception + */ + public static final QualifiedName EXCEPTION_IN_BEFORE_CACHE_UPDATE = new QualifiedName(TestPlugin.PLUGIN_ID, + "ExceptionInBeforeCacheUpdate"); + @Override public void onCacheUpdate(ISemanticFileStore semanticFileStore, InputStream newContent, long cacheTimestamp, boolean append, IProgressMonitor monitor) { + boolean useBeforeCacheUpdate = shouldUseBeforeCacheUpdate(semanticFileStore); + if (!useBeforeCacheUpdate) { + boolean writeThrough = shouldUseWriteThrough(semanticFileStore); + if (writeThrough) { + RemoteItem parentItem = getStore().getItemByPath(semanticFileStore.getPath().removeFirstSegments(2)); + if (parentItem.getType() == Type.FILE) { + OutputStream os = ((RemoteFile) parentItem).getOutputStream(append); + try { + Util.transferStreams(newContent, os, monitor); + } catch (CoreException e) { + // $JL-EXC$ TODO proper error handling + } + } + } else { + super.onCacheUpdate(semanticFileStore, newContent, cacheTimestamp, append, monitor); + } + } + } + + @Override + public void beforeCacheUpdate(ISemanticFileStore childStore, InputStream newContent, long timestamp, boolean append, + IProgressMonitor monitor) throws CoreException { + boolean useBeforeCacheUpdate = shouldUseBeforeCacheUpdate(childStore); + if (useBeforeCacheUpdate) { + if (shouldThrowExceptionInBeforeCacheUpdate(childStore)) { + throw new CoreException(new Status(IStatus.ERROR, "test", "test exception")); + } + boolean writeThrough = shouldUseWriteThrough(childStore); + if (writeThrough) { + RemoteItem parentItem = getStore().getItemByPath(childStore.getPath().removeFirstSegments(2)); + if (parentItem.getType() == Type.FILE) { + OutputStream os = ((RemoteFile) parentItem).getOutputStream(append); + try { + Util.transferStreams(newContent, os, monitor); + } catch (CoreException e) { + // $JL-EXC$ TODO proper error handling + } + } + } else { + try { + setResourceTimestamp(childStore, timestamp, monitor); + } catch (CoreException e) { + // TODO logging + } + } + } + } + + private boolean shouldUseBeforeCacheUpdate(ISemanticFileStore childStore) { + boolean useBeforeCacheUpdate; + try { + useBeforeCacheUpdate = childStore.getSessionProperty(USE_BEFORE_CACHE_UPDATE) != null; + } catch (CoreException e) { + // $JL-EXC$ ignore here + useBeforeCacheUpdate = false; + } + return useBeforeCacheUpdate; + } + + private boolean shouldThrowExceptionInBeforeCacheUpdate(ISemanticFileStore childStore) { + boolean exceptionInBeforeCacheUpdate; + try { + exceptionInBeforeCacheUpdate = childStore.getSessionProperty(EXCEPTION_IN_BEFORE_CACHE_UPDATE) != null; + } catch (CoreException e) { + // $JL-EXC$ ignore here + exceptionInBeforeCacheUpdate = false; + } + return exceptionInBeforeCacheUpdate; + } + + private boolean shouldUseWriteThrough(ISemanticFileStore semanticFileStore) { boolean writeThrough; try { writeThrough = semanticFileStore.getSessionProperty(WRITE_THROUGH) != null; @@ -76,19 +160,7 @@ // $JL-EXC$ ignore here writeThrough = false; } - if (writeThrough) { - RemoteItem parentItem = getStore().getItemByPath(semanticFileStore.getPath().removeFirstSegments(2)); - if (parentItem.getType() == Type.FILE) { - OutputStream os = ((RemoteFile) parentItem).getOutputStream(append); - try { - Util.transferStreams(newContent, os, monitor); - } catch (CoreException e) { - // $JL-EXC$ TODO proper error handling - } - } - } else { - super.onCacheUpdate(semanticFileStore, newContent, cacheTimestamp, append, monitor); - } + return writeThrough; } public void addResource(ISemanticFileStore parentStore, String name, ResourceType resourceType, IProgressMonitor monitor)
diff --git a/tests/org.eclipse.ui.resources.semantic.examples.test/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.resources.semantic.examples.test/META-INF/MANIFEST.MF index 4337be7..0bca230 100644 --- a/tests/org.eclipse.ui.resources.semantic.examples.test/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.resources.semantic.examples.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Unit Tests for the Semantic File System Examples (Incubation) Bundle-SymbolicName: org.eclipse.ui.resources.semantic.examples.test;singleton:=true -Bundle-Version: 0.4.2.qualifier +Bundle-Version: 0.5.0.qualifier Bundle-ClassPath: . Bundle-RequiredExecutionEnvironment: J2SE-1.5 Require-Bundle: org.junit4;bundle-version="4.3.1",