394770 - Introduce new API method and implementations to support offline update of deployable files.
diff --git a/org.eclipse.virgo.nano.deployer.api/src/main/java/org/eclipse/virgo/nano/deployer/api/core/ApplicationDeployer.java b/org.eclipse.virgo.nano.deployer.api/src/main/java/org/eclipse/virgo/nano/deployer/api/core/ApplicationDeployer.java
index 86a74d3..ab2b3e7 100644
--- a/org.eclipse.virgo.nano.deployer.api/src/main/java/org/eclipse/virgo/nano/deployer/api/core/ApplicationDeployer.java
+++ b/org.eclipse.virgo.nano.deployer.api/src/main/java/org/eclipse/virgo/nano/deployer/api/core/ApplicationDeployer.java
@@ -229,5 +229,14 @@
      * @return <code>true</code> if and only if the given artifact at its file's last modified time is already deployed
      */
     boolean isDeployed(URI uri);
-
+    
+    /**
+     * Determine whether or not the given artifact has been updated during the application server was stopped. The method is 
+     * convenient to track offline updates of deployable files.
+     * 
+     * @param uri location of the artifact
+     * @return <code>true</code> if the given artifact has been updated during application server was stopped
+     */
+    boolean isOfflineUpdated(URI uri);
+    
 }
diff --git a/org.eclipse.virgo.nano.deployer.hot/src/main/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListener.java b/org.eclipse.virgo.nano.deployer.hot/src/main/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListener.java
index 016a4c3..cf06084 100644
--- a/org.eclipse.virgo.nano.deployer.hot/src/main/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListener.java
+++ b/org.eclipse.virgo.nano.deployer.hot/src/main/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListener.java
@@ -1,3 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012 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:
+ *   SAP AG - initial contribution
+ *******************************************************************************/
 
 package org.eclipse.virgo.nano.deployer.hot;
 
@@ -88,6 +98,7 @@
      * 
      * Reacts to initial event in the pickup directory and calls the bulk deploy method in {@link ApplicationDeployer}.
      */
+    @Override
     public void onInitialEvent(List<String> paths) {
         this.eventLogger.log(HotDeployerLogEvents.HOT_DEPLOY_PROCESSING_FILE, FileSystemEvent.INITIAL, getConcatenatedPaths(paths));
         try {
@@ -107,20 +118,19 @@
     }
 
     /**
-     * Collects only the source artifacts that are not yet deployed and transforms the given paths to URIs.
-     * 
+     * Returns only the source artifacts' URIs that need to be deployed or updated.
      */
-    private List<URI> getNotDeployedUris(List<String> sourceArtefacts) {
-        List<URI> notDeployedFileUris = new ArrayList<URI>();
+    private List<URI> getUrisToDeploy(List<String> sourceArtefacts) {
+        List<URI> resultUris = new ArrayList<URI>();
         for (String sourceArtefact : sourceArtefacts) {
-            if (!isDeployed(sourceArtefact)) {
-                notDeployedFileUris.add(getDefinitiveUri(sourceArtefact));
+            if (!isDeployed(sourceArtefact) || isOfflineUpdated(sourceArtefact)) {
+                resultUris.add(getDefinitiveUri(sourceArtefact));
                 this.logger.info("ApplicationConditionallyDeploying path '{}'.", sourceArtefact);
             } else {
                 this.eventLogger.log(HotDeployerLogEvents.HOT_DEPLOY_SKIPPED, sourceArtefact);
             }
         }
-        return notDeployedFileUris;
+        return resultUris;
     }
 
     /**
@@ -130,7 +140,7 @@
      * @throws DeploymentException
      */
     private void bulkDeployIfNotDeployed(List<String> sourceArtefacts) throws DeploymentException {
-        this.deployer.bulkDeploy(getNotDeployedUris(sourceArtefacts), new DeploymentOptions(true, true, false));
+        this.deployer.bulkDeploy(getUrisToDeploy(sourceArtefacts), new DeploymentOptions(true, true, false));
     }
 
     /**
@@ -187,6 +197,15 @@
     }
 
     /**
+     * Determine whether there has been offline update of the given artefact.
+     * 
+     * @param sourceArtefact the source artefact URI string
+     */
+    private boolean isOfflineUpdated(String sourceArtefact) {
+        return this.deployer.isOfflineUpdated(getDefinitiveUri(sourceArtefact));
+    }
+
+    /**
      * Converts a string URI to a URI with a predictable format, particularly in the case where the string URI ends in a
      * file separator.
      * 
@@ -223,7 +242,7 @@
      * @throws DeploymentException
      */
     private void deployIfNotDeployed(String sourceArtefact, String fileName) throws DeploymentException {
-        if (!isDeployed(sourceArtefact)) {
+        if (!isDeployed(sourceArtefact) || isOfflineUpdated(sourceArtefact)) {
             deploy(sourceArtefact);
         } else {
             this.eventLogger.log(HotDeployerLogEvents.HOT_DEPLOY_SKIPPED, fileName);
diff --git a/org.eclipse.virgo.nano.deployer.hot/src/test/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListenerTests.java b/org.eclipse.virgo.nano.deployer.hot/src/test/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListenerTests.java
index 6852b6f..2fb2908 100644
--- a/org.eclipse.virgo.nano.deployer.hot/src/test/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListenerTests.java
+++ b/org.eclipse.virgo.nano.deployer.hot/src/test/java/org/eclipse/virgo/nano/deployer/hot/HotDeployerFileSystemListenerTests.java
@@ -68,6 +68,7 @@
     public void existingAppDuringStartup() throws Exception {
         File app = new File("path/to/app");
         expect(deployer.isDeployed(app.toURI())).andReturn(true);
+        expect(deployer.isOfflineUpdated(app.toURI())).andReturn(false);
         replay(deployer);
         listener.onChange("path/to/app", FileSystemEvent.INITIAL);
         verify(deployer);
diff --git a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/SimpleDeployer.java b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/SimpleDeployer.java
index bd8ce0e..8fc9f00 100644
--- a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/SimpleDeployer.java
+++ b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/SimpleDeployer.java
@@ -1,3 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012 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:
+ *   SAP AG - initial contribution
+ *******************************************************************************/
 
 package org.eclipse.virgo.nano.deployer;
 
@@ -25,6 +35,8 @@
     public boolean canServeFileType(String fileType);
 
     public boolean isDeployed(URI path);
+    
+    public boolean isOfflineUpdated(URI path);
 
     public DeploymentIdentity getDeploymentIdentity(URI path);
 
diff --git a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/BundleDeployer.java b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/BundleDeployer.java
index 68d83be..2e4c120 100644
--- a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/BundleDeployer.java
+++ b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/BundleDeployer.java
@@ -1,3 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012 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:
+ *   SAP AG - initial contribution
+ *******************************************************************************/
 
 package org.eclipse.virgo.nano.deployer.internal;
 
@@ -7,7 +17,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.URI;
-import java.net.URISyntaxException;
+import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -20,6 +30,7 @@
 import org.eclipse.virgo.nano.deployer.api.core.DeploymentIdentity;
 import org.eclipse.virgo.nano.deployer.util.BundleInfosUpdater;
 import org.eclipse.virgo.nano.deployer.util.BundleLocationUtil;
+import org.eclipse.virgo.nano.deployer.util.StatusFileModificator;
 import org.eclipse.virgo.util.io.FileCopyUtils;
 import org.eclipse.virgo.util.io.IOUtils;
 import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
@@ -45,6 +56,8 @@
 
     private static final char SLASH = '/';
 
+    private static final String PICKUP_DIR = "pickup";
+
     private static final String FRAGMEN_HOST_HEADER = "Fragment-Host";
 
     private final EventLogger eventLogger;
@@ -60,9 +73,11 @@
     private final PackageAdmin packageAdmin;
 
     private final File workBundleInstallLocation;
-    
+
     private final File kernelHomeFile;
 
+    private final File pickupDir;
+
     public BundleDeployer(BundleContext bundleContext, PackageAdmin packageAdmin, EventLogger eventLogger) {
         this.eventLogger = eventLogger;
         this.bundleContext = bundleContext;
@@ -71,13 +86,15 @@
         if (kernelHome != null) {
             this.kernelHomeFile = new File(kernelHome);
             if (this.kernelHomeFile.exists()) {
-                File bundlesInfoFile = new File(kernelHomeFile, "configuration/org.eclipse.equinox.simpleconfigurator/bundles.info");
-                this.bundleInfosUpdater = new BundleInfosUpdater(bundlesInfoFile, kernelHomeFile);
+                File bundlesInfoFile = new File(this.kernelHomeFile, "configuration/org.eclipse.equinox.simpleconfigurator/bundles.info");
+                this.bundleInfosUpdater = new BundleInfosUpdater(bundlesInfoFile, this.kernelHomeFile);
+                this.pickupDir = new File(this.kernelHomeFile, PICKUP_DIR);
                 String thisBundleName = this.bundleContext.getBundle().getSymbolicName();
                 String staging = "staging";
-                this.workBundleInstallLocation = new File(kernelHomeFile, "work" + File.separator + thisBundleName + File.separator + staging);
+                this.workBundleInstallLocation = new File(this.kernelHomeFile, "work" + File.separator + thisBundleName + File.separator + staging);
             } else {
-                throw new IllegalStateException("Required location '" + this.kernelHomeFile.getAbsolutePath() + "' does not exist. Check the value of the '"+ KERNEL_HOME_PROP +"' propery");
+                throw new IllegalStateException("Required location '" + this.kernelHomeFile.getAbsolutePath()
+                    + "' does not exist. Check the value of the '" + KERNEL_HOME_PROP + "' propery");
             }
         } else {
             throw new IllegalStateException("Missing value for required property '" + KERNEL_HOME_PROP + "'");
@@ -145,9 +162,14 @@
     @Override
     public boolean install(URI uri) {
         this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(uri).toString());
+        String jarName = extractDecodedJarNameFromString(uri.toString());
+        final long lastModified = new File(uri).lastModified();
+        StatusFileModificator.deleteStatusFile(jarName, this.pickupDir);
+
         try {
             if (!validateUri(uri) || !createInstallationFolder()) {
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, uri);
+                StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
                 return STATUS_ERROR;
             }
             File stagedFile = getStagedFile(uri);
@@ -163,8 +185,11 @@
                 refreshHosts(hostHolder, installed);
                 updateBundleInfo(installed, stagedFile, true);
             }
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, installed.getBundleId(),
+                lastModified);
         } catch (Exception e) {
             this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, e, uri);
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
             return STATUS_ERROR;
         }
         return STATUS_OK;
@@ -173,6 +198,10 @@
     @Override
     public boolean start(URI uri) {
         Bundle installedBundle = getInstalledBundle(uri);
+        String jarName = extractDecodedJarNameFromString(uri.toString());
+        final long lastModified = new File(uri).lastModified();
+        StatusFileModificator.deleteStatusFile(jarName, this.pickupDir);
+
         File stagedFile = getStagedFile(uri);
         if (installedBundle != null) {
             this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING, installedBundle.getSymbolicName(), installedBundle.getVersion());
@@ -180,17 +209,21 @@
                 if (!isFragment(installedBundle)) {
                     installedBundle.start();
                     updateBundleInfo(installedBundle, stagedFile, false);
+                    this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installedBundle.getSymbolicName(), installedBundle.getVersion());
                 } else {
-                    this.logger.warn("The installed bundle for the given url [" + uri
-                        + "] is a fragment bundle. Start operation for this url failed. ");
-                    return STATUS_ERROR;
+                    if (this.logger.isWarnEnabled()) {
+                        this.logger.warn("The installed bundle for the given url [" + uri
+                            + "] is a fragment bundle. Start operation for this url will not be executed.");
+                    }
                 }
             } catch (Exception e) {
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING_ERROR, e, installedBundle.getSymbolicName(), installedBundle.getVersion());
+                StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
                 return STATUS_ERROR;
             }
-            this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installedBundle.getSymbolicName(), installedBundle.getVersion());
         }
+        StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_OK, installedBundle.getBundleId(),
+            lastModified);
         return STATUS_OK;
     }
 
@@ -198,10 +231,14 @@
     public boolean deploy(URI path) {
         this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(path).toString());
         final File deployedFile = new File(path);
+        String jarName = extractDecodedJarNameFromString(path.toString());
+        long lastModified = deployedFile.lastModified();
+        StatusFileModificator.deleteStatusFile(jarName, this.pickupDir);
 
         if (!canWrite(path)) {
             this.logger.error("Cannot open the file " + path + " for writing. The configured timeout is " + this.largeFileCopyTimeout + ".");
             this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, path);
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
             return STATUS_ERROR;
         }
         final Bundle installed;
@@ -214,6 +251,7 @@
                     this.logger.error("Failed to create staging directory '" + this.workBundleInstallLocation.getAbsolutePath()
                         + "' for bundle deployment.");
                     this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, path);
+                    StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
                     return STATUS_ERROR;
                 }
             }
@@ -224,6 +262,7 @@
             hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagedFile);
         } catch (Exception e) {
             this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, e, path);
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
             return STATUS_ERROR;
         }
         this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLED, installed.getSymbolicName(), installed.getVersion());
@@ -245,19 +284,23 @@
                 installed.start();
             } catch (Exception e) {
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING_ERROR, e, installed.getSymbolicName(), installed.getVersion());
+                StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
                 return STATUS_ERROR;
             }
             this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installed.getSymbolicName(), installed.getVersion());
         }
         try {
             if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) {
-                String bundlesInfoLocation = BundleLocationUtil.getRelativisedURI(kernelHomeFile, stagedFile).toString();
-                BundleInfosUpdater.registerToBundlesInfo(installed, bundlesInfoLocation, hostHolder != null && hostHolder.getBundleSymbolicName() != null);
+                String bundlesInfoLocation = BundleLocationUtil.getRelativisedURI(this.kernelHomeFile, stagedFile).toString();
+                BundleInfosUpdater.registerToBundlesInfo(installed, bundlesInfoLocation, hostHolder != null
+                    && hostHolder.getBundleSymbolicName() != null);
             }
         } catch (Exception e) {
             this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, e, installed.getSymbolicName(), installed.getVersion());
         }
 
+        StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_OK, installed.getBundleId(),
+            lastModified);
         return STATUS_OK;
     }
 
@@ -278,11 +321,15 @@
     @Override
     public boolean update(URI path) {
         final File updatedFile = new File(path);
+        final String jarName = extractDecodedJarNameFromString(path.toString());
+        long lastModified = updatedFile.lastModified();
         final File matchingStagedFile = new File(this.workBundleInstallLocation, extractJarFileNameFromString(path.toString()));
+        StatusFileModificator.deleteStatusFile(jarName, this.pickupDir);
 
         if (!canWrite(path)) {
             this.logger.error("Cannot open the file [" + path + "] for writing. Timeout is [" + this.largeFileCopyTimeout + "].");
             this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATING_ERROR, path);
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
             return STATUS_ERROR;
         }
 
@@ -301,25 +348,34 @@
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATED, bundle.getSymbolicName(), bundle.getVersion());
             } catch (Exception e) {
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_UPDATE_ERROR, e, bundle.getSymbolicName(), bundle.getVersion());
+                StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_ERROR, -1, lastModified);
+                return STATUS_ERROR;
             }
         } else {
             deploy(path);
         }
+        StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_DEPLOY, STATUS_OK, bundle.getBundleId(),
+            lastModified);
         return STATUS_OK;
     }
 
     @Override
     public boolean undeploy(Bundle bundle) {
         if (bundle != null) {
-            File stagingFileToDelete = new File(bundle.getLocation().substring(BundleLocationUtil.REFERENCE_FILE_PREFIX.length()));
+            String bundleLocation = bundle.getLocation();
+            File stagingFileToDelete = new File(bundleLocation.substring(BundleLocationUtil.REFERENCE_FILE_PREFIX.length()));
+            String jarName = extractDecodedJarNameFromString(bundleLocation);
+            StatusFileModificator.deleteStatusFile(jarName, this.pickupDir);
+
             final FragmentHost hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagingFileToDelete);
             try {
                 if (this.logger.isInfoEnabled()) {
                     this.logger.info("Removing bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion() + "' from bundles.info.");
                 }
                 if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) {
-                    String bundlesInfoLocation = BundleLocationUtil.getRelativisedURI(kernelHomeFile, stagingFileToDelete).toString();
-                    BundleInfosUpdater.unregisterToBundlesInfo(bundle, bundlesInfoLocation, hostHolder != null && hostHolder.getBundleSymbolicName() != null);
+                    String bundlesInfoLocation = BundleLocationUtil.getRelativisedURI(this.kernelHomeFile, stagingFileToDelete).toString();
+                    BundleInfosUpdater.unregisterToBundlesInfo(bundle, bundlesInfoLocation, hostHolder != null
+                        && hostHolder.getBundleSymbolicName() != null);
                     this.logger.info("Successfully removed bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion()
                         + "' from bundles.info.");
                 } else {
@@ -344,8 +400,11 @@
                 }
             } catch (BundleException e) {
                 this.eventLogger.log(NanoDeployerLogEvents.NANO_UNDEPLOY_ERROR, e, bundle.getSymbolicName(), bundle.getVersion());
+                StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_UNDEPLOY, STATUS_ERROR, -1, -1);
                 return STATUS_ERROR;
             }
+
+            StatusFileModificator.createStatusFile(jarName, this.pickupDir, StatusFileModificator.OP_UNDEPLOY, STATUS_OK, -1, -1);
         }
         return STATUS_OK;
     }
@@ -373,6 +432,22 @@
     }
 
     @Override
+    public boolean isOfflineUpdated(URI path) {
+        final String jarName = extractDecodedJarNameFromString(path.toString());
+        final File deployFile = new File(path);
+        long deployFileLastModified = deployFile.lastModified();
+        if (deployFileLastModified == StatusFileModificator.getLastModifiedFromStatusFile(jarName, this.pickupDir)) {
+            return false;
+        }
+        return true;
+    }
+
+    private String extractDecodedJarNameFromString(String path) {
+        final String jarName = path.substring(path.lastIndexOf(SLASH) + 1, path.length() - 4);
+        return URLDecoder.decode(jarName);
+    }
+
+    @Override
     public DeploymentIdentity getDeploymentIdentity(URI path) {
         File matchingStagingBundle = getStagedFile(path);
         Bundle bundle = this.bundleContext.getBundle(BundleLocationUtil.createInstallLocation(this.kernelHomeFile, matchingStagingBundle));
diff --git a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/StandardApplicationDeployer.java b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/StandardApplicationDeployer.java
index 482959b..3956938 100644
--- a/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/StandardApplicationDeployer.java
+++ b/org.eclipse.virgo.nano.deployer/src/main/java/org/eclipse/virgo/nano/deployer/internal/StandardApplicationDeployer.java
@@ -1,3 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012 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:
+ *   SAP AG - initial contribution
+ *******************************************************************************/
 
 package org.eclipse.virgo.nano.deployer.internal;
 
@@ -30,7 +40,7 @@
 public class StandardApplicationDeployer implements ApplicationDeployer {
 
     private static final String TOPIC_RECOVERY_COMPLETED = "org/eclipse/virgo/kernel/deployer/recovery/COMPLETED";
-    
+
     private EventLogger eventLogger;
 
     private PackageAdmin packageAdmin;
@@ -53,7 +63,7 @@
         this.bundleContext = context.getBundleContext();
         this.defaultDeployer = new BundleDeployer(context.getBundleContext(), this.packageAdmin, this.eventLogger);
         this.simpleDeployers.add(this.defaultDeployer);
-        
+
         recoveryComplete();
         initialiseHotDeployer();
 
@@ -171,6 +181,16 @@
         return false;
     }
 
+    @Override
+    public boolean isOfflineUpdated(URI uri) {
+        for (SimpleDeployer deployer : this.simpleDeployers) {
+            if (deployer.canServeFileType(getFileTypeFromUri(uri))) {
+                return deployer.isOfflineUpdated(uri);
+            }
+        }
+        return false;
+    }
+
     private String getFileTypeFromUri(URI uri) {
         String path = uri.toString();
         return path.substring(path.lastIndexOf(".") + 1);
@@ -238,7 +258,7 @@
     public void unbindEventLogger(EventLogger logger) {
         this.eventLogger = null;
     }
-    
+
     public void bindEventAdmin(EventAdmin admin) {
         this.eventAdmin = admin;
     }
@@ -322,7 +342,7 @@
     }
 
     private void recoveryComplete() {
-        eventAdmin.postEvent(new Event(TOPIC_RECOVERY_COMPLETED, (Map<String, ?>)null));
+        this.eventAdmin.postEvent(new Event(TOPIC_RECOVERY_COMPLETED, (Map<String, ?>) null));
     }
-    
+
 }