Bug 389423 - [bulk] Adapt the Nano Simple 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 9a69160..562fe8f 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
@@ -7,13 +7,16 @@
 import org.eclipse.virgo.nano.deployer.api.core.DeploymentIdentity;
 import org.osgi.framework.Bundle;
 
-
 public interface SimpleDeployer {
     
     public final int HOT_DEPLOYED_ARTIFACTS_START_LEVEL = 5;
 
     public boolean deploy(URI path);
     
+    public boolean install(URI path);
+    
+    public boolean start(URI path);
+    
     public boolean update(URI path);
     
     public boolean undeploy(Bundle bundle);
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 8e0e38d..8ac1211 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
@@ -9,6 +9,7 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.jar.JarFile;
 
@@ -48,6 +49,8 @@
     private static final String UNKNOWN = "unknown";
 
     private static final String INSTALL_BY_REFERENCE_PREFIX = "reference:file:";
+    
+    private static final String FRAGMEN_HOST_HEADER = "Fragment-Host";
 
     private final EventLogger eventLogger;
 
@@ -75,7 +78,123 @@
         String staging = "staging";
         this.workBundleInstallLocation = new File(kernelHomeFile, "work" + File.separator + thisBundleName + File.separator + staging);
     }
+    
+    private Boolean createInstallationFolder(){
+        if (!this.workBundleInstallLocation.exists()) {
+            if (!this.workBundleInstallLocation.mkdirs()) {
+                this.logger.error("Failed to create staging directory '" + this.workBundleInstallLocation.getAbsolutePath()
+                    + "' for bundle deployment.");
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    private boolean validateUri(URI uri){
+		if (!canWrite(uri)) {
+            this.logger.error("Cannot open the file " + uri + " for writing. The configured timeout is " + this.largeFileCopyTimeout + ".");
+            return false;
+		}
+		return true;
+    }
+    
+    private Boolean isFragment(FragmentHost hostHolder){
+    	return (hostHolder != null && hostHolder.getBundleSymbolicName() != null);
+    }
+    
+    private Boolean isFragment(Bundle bundle){
+    	Enumeration<String> keys = bundle.getHeaders().keys();
+    	while (keys.hasMoreElements()){
+    		if (keys.nextElement().equalsIgnoreCase(FRAGMEN_HOST_HEADER) ){
+    			return true;
+    		}
+    	}
+    	return false;
+    }
+    
+    private void refreshHosts(FragmentHost hostHolder, Bundle fragment){
+    	 try {
+             Bundle[] hosts = this.packageAdmin.getBundles(hostHolder.getBundleSymbolicName(), null);
+             if (hosts != null) {
+             	this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHING_HOST, fragment.getSymbolicName(), fragment.getVersion());
+             	this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHING_HOST, fragment.getSymbolicName(), fragment.getVersion());
+                 this.packageAdmin.refreshPackages(hosts);
+                 this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESHED_HOST, fragment.getSymbolicName(), fragment.getVersion());
+             }
+         } catch (Exception e) {
+             this.eventLogger.log(NanoDeployerLogEvents.NANO_REFRESH_HOST_ERROR, e, fragment.getSymbolicName(), fragment.getVersion());
+         }
+         try {
+             if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) {
+                 registerToBundlesInfo(fragment, true);
+             }
+         } catch (Exception e) {
+             this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, e, fragment.getSymbolicName(), fragment.getVersion());
+         }
+    }
+    
+    private void updateBundleInfo(Bundle bundle, Boolean isFragment){
+    	try {
+            if (this.bundleInfosUpdater != null && this.bundleInfosUpdater.isAvailable()) {
+                registerToBundlesInfo(bundle, true);
+            }
+        } catch (Exception ex) {
+            this.eventLogger.log(NanoDeployerLogEvents.NANO_PERSIST_ERROR, ex, bundle.getSymbolicName(), bundle.getVersion());
+        }
+    }
+    
+	@Override
+	public boolean install(URI uri) {
+		this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(uri).toString());
+		try{
+			if (!validateUri(uri) || !(createInstallationFolder())) {
+				this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, uri);
+				return STATUS_ERROR;
+			}
+			// copy bundle to work folder
+			File stagedFile = new File(workBundleInstallLocation, extractJarFileNameFromString(uri.toString()));
+			FileCopyUtils.copy(new File(uri), stagedFile);
+			
+			// install the bundle
+			final Bundle installed = this.bundleContext.installBundle(createInstallLocation(stagedFile));
+			final FragmentHost hostHolder = getFragmentHostFromDeployedBundleIfExsiting(stagedFile);
+			this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLED, installed.getSymbolicName(), installed.getVersion());
+			
+			//if fragment, refresh hosts and update bundles.info
+			if (isFragment(hostHolder)){
+				refreshHosts(hostHolder, installed);
+				updateBundleInfo(installed, true);
+			}
+		} catch (Exception e) {
+			this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING_ERROR, e, uri);
+			return STATUS_ERROR;
+		}            
+		return STATUS_OK;
+	}
+	
 
+	@Override
+	public boolean start(URI uri) {
+		Bundle installedBundle = getInstalledBundle(uri);
+		if (installedBundle != null){
+			this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING, installedBundle.getSymbolicName(), installedBundle.getVersion());
+			try {
+				if (!isFragment(installedBundle)){
+					installedBundle.start();
+					updateBundleInfo(installedBundle, false);
+				}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;
+				}
+			} catch (Exception e) {
+				this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTING_ERROR, e, installedBundle.getSymbolicName(), installedBundle.getVersion());
+				return STATUS_ERROR;
+			}
+			this.eventLogger.log(NanoDeployerLogEvents.NANO_STARTED, installedBundle.getSymbolicName(), installedBundle.getVersion());
+		}
+			return STATUS_OK;
+	}
+    
     @Override
     public boolean deploy(URI path) {
         this.eventLogger.log(NanoDeployerLogEvents.NANO_INSTALLING, new File(path).toString());
@@ -233,6 +352,11 @@
     public boolean canServeFileType(String fileType) {
         return JAR.equals(fileType);
     }
+    
+    private Bundle getInstalledBundle(URI uri){
+    	File matchingStagingBundle = new File(this.workBundleInstallLocation, extractJarFileNameFromString(uri.toString()));
+        return this.bundleContext.getBundle(createInstallLocation(matchingStagingBundle));
+    }
 
     @Override
     public boolean isDeployed(URI path) {
@@ -344,4 +468,6 @@
 		types.add(JAR);
 		return types;
 	}
+
+
 }
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 cd7ed15..0e0423b 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
@@ -84,15 +84,36 @@
             }
         }
         if (!isThereSuitableDeployer) {
-        	List<String> acceptedTypes = new ArrayList<String>();
-        	for (SimpleDeployer deployer : this.simpleDeployers) {
-        		acceptedTypes.addAll(deployer.getAcceptedFileTypes());
-        	}
-        	this.eventLogger.log(NanoDeployerLogEvents.NANO_UNRECOGNIZED_TYPE, uri, acceptedTypes);
+        	handleUnsupportedFileType(uri);
         }
         return null;
     }
-
+    
+	@Override
+	public DeploymentIdentity[] bulkDeploy(List<URI> uris,
+			DeploymentOptions options) throws DeploymentException {
+		if (uris != null && !uris.isEmpty()) {
+			installDeployables(uris);
+			startDeployables(uris);
+			return getDeploymentIdentities(uris);
+		}else{
+			this.logger
+			.warn("Cannot perform bulk deploy operation of the given URIs list as it is either empty or null.");
+			return null;
+		}
+	}
+    
+    private DeploymentIdentity[] getDeploymentIdentities(List<URI> uris){
+    	List<DeploymentIdentity> accumulatedDIs = new ArrayList<DeploymentIdentity>();
+    	for(URI uri:uris){
+    		DeploymentIdentity di = getDeploymentIdentity(uri);
+    		if (di != null){
+    			accumulatedDIs.add(di);
+    		}
+    	}
+    	return accumulatedDIs.toArray(new DeploymentIdentity[0]);
+    }
+ 
     @Override
     public void undeploy(DeploymentIdentity deploymentIdentity) throws DeploymentException {
         if (this.bundleContext != null) {
@@ -234,5 +255,55 @@
     public void unbindSimpleDeployer(SimpleDeployer deployer) {
         this.simpleDeployers.remove(deployer);
     }
+    
+    private void handleUnsupportedFileType(URI uri){
+    	List<String> acceptedTypes = new ArrayList<String>();
+		for (SimpleDeployer deployer : this.simpleDeployers) {
+			acceptedTypes.addAll(deployer.getAcceptedFileTypes());
+		}
+		this.eventLogger.log(NanoDeployerLogEvents.NANO_UNRECOGNIZED_TYPE, uri, acceptedTypes);
+    }
+    
+    private void installDeployables(List<URI> uris){
+    	for (URI uri:uris){
+        	boolean isThereSuitableDeployer = false;
+    		for (SimpleDeployer deployer : this.simpleDeployers) {
+    			if (deployer.canServeFileType(getFileTypeFromUri(uri))) {
+    				isThereSuitableDeployer = true;
+    				if (!deployer.isDeployFileValid(new File(uri))) {
+    					this.eventLogger.log(NanoDeployerLogEvents.NANO_INVALID_FILE);
+    				} else {
+    					if (deployer.isDeployed(uri)) { 
+    						deployer.update(uri);
+    					} else {
+    						deployer.install(uri);
+    					}
+    				}
+    			}
+    		}
+    		if (!isThereSuitableDeployer) {
+    			handleUnsupportedFileType(uri);
+    		}
+    	}
+    }
+
+    private  void startDeployables (List<URI> uris){
+    	for (URI uri:uris){
+        	boolean isThereSuitableDeployer = false;
+    		for (SimpleDeployer deployer : this.simpleDeployers) {
+    			if (deployer.canServeFileType(getFileTypeFromUri(uri))) {
+    				isThereSuitableDeployer = true;
+    				if (!deployer.isDeployFileValid(new File(uri))) {
+    					this.eventLogger.log(NanoDeployerLogEvents.NANO_INVALID_FILE);
+    				} else {
+    						deployer.start(uri);
+    				}    				
+    			}
+    		}
+    		if (!isThereSuitableDeployer) {
+    			handleUnsupportedFileType(uri);
+    		}
+    	}
+    }
 
 }