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",