499979: Deployment of nested plans fails 

Originally, if a top-level plan referred to another plan the deployment
of the top-level plan would fail because the tooling did not take into
account the nested plan when deploying the top-level plan.

Now nested plans are supported:
- JmxServerDeployCommand parses the top-level plan to look for
  references to nested plans
- If found, nested plans are copied to the stage directory before
  deploying the top-level plan
  
Implementaiton details
----------------------
The code tries to deal with the case that plans are distributed across
the workspace in multiple projects. If the top-level plan is contained
in a Java project and such Java project depends on other Java projects,
the project dependencies will be scanned in the proper class-path order
when looking for nested plans and in case of duplicates, the first
matching plan found in the cass-path will be used.

If the project containing the top-level project is not a Java project,
nested plans will be searched only within the top-level project.

While the tooling do their best to support plans, please not that the
above only accounts for the most typical use cases and that certain
corner cases won't be optimally managed. For example, if a nested plan
is changed the server will not be able to detect this and auto-publish
the nested plan and refresh the top-level plan. The user who changed the
nested plan will have to un-deploy and re-deploy the top-level plan to
see the change. The modified nested plan will however be picked at the
next server restart.

This limitation exists because currently the Tools do not create a WST
module for the whole project containing the plan, but rather a WST
module for the plan file only. This design choice implies that the WST
infrastructure will be tracking workspace changes related to the
top-level plan file only. To change this behavior a significant rewrite
would be required and the effort is in my opinion not worth the benefit.
diff --git a/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/core/FacetUtils.java b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/core/FacetUtils.java
index 4c1d5b0..de17097 100644
--- a/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/core/FacetUtils.java
+++ b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/core/FacetUtils.java
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     SpringSource, a division of VMware, Inc. - initial API and implementation
+ *     GianMaria Romanato - add utilities for management of nested plans
  *******************************************************************************/
 
 package org.eclipse.virgo.ide.facet.core;
@@ -19,15 +20,25 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
 
+import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
@@ -37,8 +48,14 @@
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
 import org.eclipse.emf.ecore.xmi.PackageNotFoundException;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.virgo.ide.facet.internal.core.Plan;
+import org.eclipse.virgo.ide.facet.internal.core.PlanReader;
+import org.eclipse.virgo.ide.facet.internal.core.PlanReference;
 import org.eclipse.virgo.ide.par.Bundle;
 import org.eclipse.virgo.ide.par.Par;
 import org.eclipse.virgo.ide.par.ParPackage;
@@ -49,6 +66,7 @@
  *
  * @author Christian Dupuis
  * @author Leo Dos Santos
+ * @author GianMaria Romanato
  * @since 1.0.0
  */
 public class FacetUtils {
@@ -203,4 +221,222 @@
         return null;
     }
 
+    /**
+     * Gets all the plan files found in the given project.
+     * 
+     * @param project
+     * @return
+     */
+    public static Collection<IFile> getPlansInPlanProject(IProject project) {
+        if (!isPlanProject(project)) {
+            return Collections.emptyList();
+        }
+
+        final List<IFile> planFiles = new ArrayList<IFile>();
+
+        // Collect output locations if java project
+        final Set<IPath> outputLocations = new HashSet<IPath>();
+        try {
+            if (FacetUtils.hasNature(project, JavaCore.NATURE_ID)) {
+                IJavaProject je = JavaCore.create(project);
+                try {
+                    outputLocations.add(je.getOutputLocation());
+                    for (IClasspathEntry entry : je.getRawClasspath()) {
+                        if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+                            if (entry.getOutputLocation() != null) {
+                                outputLocations.add(entry.getOutputLocation());
+                            }
+                        }
+                    }
+                } catch (JavaModelException e) {
+                    // safe to ignore
+                }
+            }
+            project.accept(new IResourceVisitor() {
+
+                public boolean visit(IResource resource) throws CoreException {
+                    if (resource.isTeamPrivateMember() || resource.isDerived()) {
+                        return false;
+                    }
+                    if (resource instanceof IFile && "plan".equals(resource.getFileExtension())) {
+                        planFiles.add((IFile) resource);
+                    } else if (resource instanceof IContainer) {
+                        IPath path = ((IContainer) resource).getFullPath();
+                        for (IPath outputLocation : outputLocations) {
+                            if (outputLocation.isPrefixOf(path)) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    }
+                    return true;
+                }
+            });
+        } catch (CoreException e) {
+            // TODO CD log exception
+        }
+
+        return planFiles;
+    }
+
+    /**
+     * Returns all the plans in the workspace as a map project to list of plan files
+     *
+     * @return
+     */
+    private static Map<IProject, Collection<IFile>> getPlansInWorkspace() {
+        Map<IProject, Collection<IFile>> plans = new HashMap<IProject, Collection<IFile>>();
+        IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+        for (IProject iProject : projects) {
+            if (iProject.isOpen() && isPlanProject(iProject)) {
+                Collection<IFile> ps = getPlansInPlanProject(iProject);
+                plans.put(iProject, ps);
+            }
+        }
+        return plans;
+    }
+
+    /**
+     * Returns the list of nested plans for a given plan file. The look only in the planFile project, unless the project
+     * is also a Java project. In such case, the project classpath is used to look for plans in required projects.
+     *
+     * @param planFile
+     * @return
+     */
+    public static List<IFile> getNestedPlanFiles(IFile planFile) {
+        if (!isPlanProject(planFile)) {
+            return Collections.emptyList();
+        }
+
+        // parse the top level plan file
+        PlanReader reader = new PlanReader();
+        Plan topLevelPlan;
+        try {
+            topLevelPlan = reader.read(planFile.getContents());
+        } catch (Exception e1) {
+            return Collections.emptyList();
+        }
+
+        List<PlanReference> nestedReferences = topLevelPlan.getNestedPlans();
+
+        if (nestedReferences.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<IFile> nestedPlanFiles = new ArrayList<IFile>();
+
+        /*
+         * examine the containing Java project class path (if a Java projecT) and find all the required Java projects.
+         * Nested plans will be searched not only in the current project but also in its dependencies
+         */
+        List<IProject> orderedProjects = getOrderedProjectDependencies(planFile.getProject());
+        orderedProjects.add(0, planFile.getProject());
+
+        Map<IProject, Collection<IFile>> allPlans = FacetUtils.getPlansInWorkspace();
+
+        // used for searching a nested plan that is referred only by name (no version)
+        Map<String, Plan> name2PlanLookup = new HashMap<String, Plan>();
+
+        // used for searching a nested plan that is referred by name and version
+        Map<PlanReference, Plan> ref2Plan = new HashMap<PlanReference, Plan>();
+
+        // plan to related file
+        Map<PlanReference, IFile> ref2File = new HashMap<PlanReference, IFile>();
+
+        // loop over the list of ordered projects and search for plans
+        for (IProject iProject : orderedProjects) {
+            Collection<IFile> candidates = allPlans.get(iProject);
+            if (candidates != null) {
+                for (IFile iFile : candidates) {
+                    // ignore self
+                    if (!planFile.equals(iFile)) {
+                        try {
+                            Plan p = reader.read(iFile.getContents());
+                            PlanReference r = p.asRefence();
+
+                            /*
+                             * in case of duplicate plans (same name and version) first found in classpath wins and
+                             * found is assumed to be the right one
+                             */
+                            if (!ref2Plan.containsKey(r)) {
+                                // add for name+version lookup
+                                ref2Plan.put(r, p);
+
+                                /*
+                                 * in case of duplicates plans with the same name and different version if an outer plan
+                                 * is referring to a nested plan via name only, the first found in classpath wins
+                                 */
+                                if (!name2PlanLookup.containsKey(r.getName())) {
+                                    name2PlanLookup.put(r.getName(), p);
+                                }
+
+                                ref2File.put(r, iFile);
+
+                            }
+                        } catch (Exception e) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        }
+
+        // finally compute the list of nested plans. Use a queue instead of recursion.
+        Queue<PlanReference> toBeProcessed = new ArrayBlockingQueue<PlanReference>(ref2Plan.size() + 1);
+        Set<PlanReference> alreadyProcessed = new HashSet<PlanReference>();
+        toBeProcessed.addAll(nestedReferences);
+
+        while (toBeProcessed.peek() != null) {
+            PlanReference planReference = toBeProcessed.poll();
+            alreadyProcessed.add(planReference);
+
+            // search for exact match name + version
+            Plan nestedPlan = ref2Plan.get(planReference); // search for exact match name + version
+            if (nestedPlan == null && planReference.getVersion() == null) {
+                nestedPlan = name2PlanLookup.get(planReference.getName());
+            }
+
+            if (nestedPlan != null) {
+                IFile nestedFile = ref2File.get(nestedPlan.asRefence());
+
+                nestedPlanFiles.add(nestedFile);
+
+                for (PlanReference aRef : nestedPlan.getNestedPlans()) {
+                    if (!alreadyProcessed.contains(aRef)) {
+                        toBeProcessed.add(aRef);
+                    }
+                }
+            }
+        }
+
+        return nestedPlanFiles;
+    }
+
+    /**
+     * Returns the ordered list of project dependencies for the given Java project or an empty list if the project is
+     * not a Java project.
+     *
+     * @param project the Java project
+     * @return the list of required projects
+     */
+    private static List<IProject> getOrderedProjectDependencies(IProject project) {
+        LinkedHashSet<IProject> projects = new LinkedHashSet<IProject>();
+        if (FacetUtils.hasNature(project, JavaCore.NATURE_ID)) {
+            IJavaProject je = JavaCore.create(project);
+            String[] names;
+            try {
+                names = je.getRequiredProjectNames();
+                for (String prjName : names) {
+                    IProject prj = ResourcesPlugin.getWorkspace().getRoot().getProject(prjName);
+                    if (prj.exists() && prj.isOpen() && isPlanProject(prj)) {
+                        projects.add(prj);
+                        projects.addAll(getOrderedProjectDependencies(prj));
+                    }
+                }
+            } catch (JavaModelException e) {
+            }
+        }
+        return new ArrayList<IProject>(projects);
+    }
+
 }
diff --git a/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/Plan.java b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/Plan.java
new file mode 100644
index 0000000..ee38b4e
--- /dev/null
+++ b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/Plan.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ *  Copyright (c) 2016 GianMaria Romanato
+ *  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:
+ *     GianMaria Romanato - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.virgo.ide.facet.internal.core;
+
+import java.util.List;
+
+import org.osgi.framework.Version;
+
+/**
+ * Represents a plan in the workspace and includes the list of referred plans. Note that this class does not intend to
+ * represent the full content of a plan file, just the minimum needed by the Tooling for deploying plans to the Virgo
+ * Runtime.
+ */
+public final class Plan extends PlanReference {
+
+    public PlanReference asRefence() {
+        return new PlanReference(getName(), getVersion());
+    }
+
+    private final List<PlanReference> nestedPlans;
+
+    /* default */ Plan(String name, Version version, List<PlanReference> nestedPlans) {
+        super(name, version);
+        this.nestedPlans = nestedPlans;
+    }
+
+    public List<PlanReference> getNestedPlans() {
+        return nestedPlans;
+    }
+
+}
diff --git a/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReader.java b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReader.java
new file mode 100644
index 0000000..975e382
--- /dev/null
+++ b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReader.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ *  Copyright (c) 2016 GianMaria Romanato
+ *  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:
+ *     GianMaria Romanato - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.virgo.ide.facet.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.osgi.framework.Version;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * A reader that builds a minimal plan descriptor for a plan file in the workspace. This class is a simplified version
+ * of a similar class existing in the Virgo Server runtime. This parser is not able to capture all the features of a
+ * plan; it just creates a minimal Plan object which lists the plan name, version and any other referred plan. Other
+ * type of child artifacts (e.g. bundles) or plan attributes (e.g. URI) are ignored as they are not relevant for the
+ * purpose of deploying workspace plan to the Virgo Runtime Environment.
+ *
+ */
+public final class PlanReader {
+
+    private static final String PLAN = "plan"; //$NON-NLS-1$
+
+    private static final String TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$
+
+    private static final String NAME_ATTRIBUTE = "name"; //$NON-NLS-1$
+
+    private static final String VERSION_ATTRIBUTE = "version"; //$NON-NLS-1$
+
+    private static final String ARTIFACT_ELEMENT = "artifact"; //$NON-NLS-1$
+
+    private static final String ATTRIBUTE_ELEMENT = "attribute"; //$NON-NLS-1$
+
+    private static final String VALUE_ATTRIBUTE = "value"; //$NON-NLS-1$
+
+    private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; //$NON-NLS-1$
+
+    private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; //$NON-NLS-1$
+
+    /**
+     * Creates a {@link PlanDescriptor} meta-data artifact from an {@link InputStream}
+     *
+     * @param inputStream from which the plan is to be read
+     * @return The plan descriptor (meta-data) from the input stream
+     */
+    public Plan read(InputStream inputStream) {
+        try {
+            Document doc = readDocument(inputStream);
+            Element element = doc.getDocumentElement();
+            return parsePlanElement(element);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to read plan descriptor", e);
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private Document readDocument(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
+        DocumentBuilder builder = createDocumentBuilderFactory().newDocumentBuilder();
+        builder.setErrorHandler(new ErrorHandler() {
+
+            public void warning(SAXParseException exception) throws SAXException {
+            }
+
+            public void fatalError(SAXParseException exception) throws SAXException {
+                throw exception;
+            }
+
+            public void error(SAXParseException exception) throws SAXException {
+                throw exception;
+            }
+        });
+        return builder.parse(inputStream);
+    }
+
+    private DocumentBuilderFactory createDocumentBuilderFactory() {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(false);
+        factory.setNamespaceAware(false);
+        factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
+        return factory;
+    }
+
+    private Plan parsePlanElement(Element element) {
+        String name = element.getAttribute(NAME_ATTRIBUTE);
+        Version version = new Version(element.getAttribute(VERSION_ATTRIBUTE));
+
+        Properties attributes = parseAttributes(element);
+        List<PlanReference> nestedPlans = parseNestedPlans(element.getElementsByTagName(ARTIFACT_ELEMENT), attributes);
+
+        return new Plan(name, version, nestedPlans);
+    }
+
+    private Properties parseAttributes(Element element) {
+        Properties result = new Properties();
+        NodeList attributeElements = element.getElementsByTagName(ATTRIBUTE_ELEMENT);
+        for (int x = 0; x < attributeElements.getLength(); x++) {
+            Element attribute = (Element) attributeElements.item(x);
+
+            String name = attribute.getAttribute(NAME_ATTRIBUTE);
+            String value = attribute.getAttribute(VALUE_ATTRIBUTE);
+
+            result.put(name, value);
+        }
+        return result;
+    }
+
+    private List<PlanReference> parseNestedPlans(NodeList artifactElements, Properties attributes) {
+        List<PlanReference> refs = new ArrayList<PlanReference>();
+        for (int i = 0; i < artifactElements.getLength(); i++) {
+            Element artifactElement = (Element) artifactElements.item(i);
+
+            String type = artifactElement.getAttribute(TYPE_ATTRIBUTE);
+            if (PLAN.equals(type)) {
+                String name = artifactElement.getAttribute(NAME_ATTRIBUTE);
+                String version = artifactElement.getAttribute(VERSION_ATTRIBUTE);
+                if (version != null && !version.isEmpty()) {
+                    refs.add(new PlanReference(name, new Version(version)));
+                } else {
+                    refs.add(new PlanReference(name, null));
+                }
+            }
+        }
+
+        return refs;
+    }
+
+}
diff --git a/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReference.java b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReference.java
new file mode 100644
index 0000000..f96bfb9
--- /dev/null
+++ b/org.eclipse.virgo.ide.facet.core/src/org/eclipse/virgo/ide/facet/internal/core/PlanReference.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ *  Copyright (c) 2016 GianMaria Romanato
+ *  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:
+ *     GianMaria Romanato - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.virgo.ide.facet.internal.core;
+
+import org.osgi.framework.Version;
+
+/**
+ * Represents a reference to a nested plan. Provides a proper implementation of {@link #equals(Object)} and
+ * {@link #hashCode()} and can be used in collections for structural equality.
+ */
+public class PlanReference {
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PlanReference other = (PlanReference) obj;
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (version == null) {
+            if (other.version != null) {
+                return false;
+            }
+        } else if (!version.equals(other.version)) {
+            return false;
+        }
+        return true;
+    }
+
+    public PlanReference(String name, Version version) {
+        super();
+        this.name = name;
+        this.version = version;
+    }
+
+    protected final String name;
+
+    protected final Version version;
+
+    public String getName() {
+        return name;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.virgo.ide.manifest.core/src/org/eclipse/virgo/ide/module/core/ServerModuleFactoryDelegate.java b/org.eclipse.virgo.ide.manifest.core/src/org/eclipse/virgo/ide/module/core/ServerModuleFactoryDelegate.java
index 2f82438..a137701 100644
--- a/org.eclipse.virgo.ide.manifest.core/src/org/eclipse/virgo/ide/module/core/ServerModuleFactoryDelegate.java
+++ b/org.eclipse.virgo.ide.manifest.core/src/org/eclipse/virgo/ide/module/core/ServerModuleFactoryDelegate.java
@@ -12,11 +12,11 @@
 package org.eclipse.virgo.ide.module.core;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -24,10 +24,6 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
-import org.eclipse.jdt.core.IClasspathEntry;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.virgo.ide.facet.core.FacetCorePlugin;
 import org.eclipse.virgo.ide.facet.core.FacetUtils;
 import org.eclipse.wst.server.core.IModule;
@@ -75,46 +71,11 @@
 
         // Every project can also be a plan project
         if (FacetUtils.isPlanProject(project)) {
-
-            // Collect output locations if java project
-            final Set<IPath> outputLocations = new HashSet<IPath>();
-            try {
-                if (FacetUtils.hasNature(project, JavaCore.NATURE_ID)) {
-                    IJavaProject je = JavaCore.create(project);
-                    try {
-                        outputLocations.add(je.getOutputLocation());
-                        for (IClasspathEntry entry : je.getRawClasspath()) {
-                            if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
-                                if (entry.getOutputLocation() != null) {
-                                    outputLocations.add(entry.getOutputLocation());
-                                }
-                            }
-                        }
-                    } catch (JavaModelException e) {
-                        // safe to ignore
-                    }
-                }
-                project.accept(new IResourceVisitor() {
-
-                    public boolean visit(IResource resource) throws CoreException {
-                        if (resource instanceof IFile && resource.getName().endsWith(".plan")) {
-                            modules.add(createModule(resource.getFullPath().toString(),
-                                resource.getProject().getName() + "/" + resource.getProjectRelativePath().toString(), FacetCorePlugin.PLAN_FACET_ID,
-                                "2.0", project));
-                        } else if (resource instanceof IContainer) {
-                            IPath path = ((IContainer) resource).getFullPath();
-                            for (IPath outputLocation : outputLocations) {
-                                if (outputLocation.isPrefixOf(path)) {
-                                    return false;
-                                }
-                            }
-                            return true;
-                        }
-                        return true;
-                    }
-                });
-            } catch (CoreException e) {
-                // TODO CD log exception
+            Collection<IFile> files = FacetUtils.getPlansInPlanProject(project);
+            for (IFile resource : files) {
+                modules.add(createModule(resource.getFullPath().toString(),
+                    resource.getProject().getName() + "/" + resource.getProjectRelativePath().toString(), FacetCorePlugin.PLAN_FACET_ID, "2.0",
+                    project));
             }
         }
         return modules.toArray(new IModule[modules.size()]);
diff --git a/org.eclipse.virgo.ide.runtime.core/META-INF/MANIFEST.MF b/org.eclipse.virgo.ide.runtime.core/META-INF/MANIFEST.MF
index 3cb568f..9b07734 100644
--- a/org.eclipse.virgo.ide.runtime.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.virgo.ide.runtime.core/META-INF/MANIFEST.MF
@@ -51,8 +51,7 @@
  org.eclipse.libra.framework.editor.core;bundle-version="0.1.0",
  org.eclipse.libra.framework.editor.ui;bundle-version="0.1.0",
  org.eclipse.jdt.ui
-Import-Package: org.apache.commons.collections.collection,
- org.apache.commons.lang,
+Import-Package: org.apache.commons.lang,
  org.apache.commons.logging
 Export-Package: org.eclipse.virgo.ide.bundlerepository.domain,
  org.eclipse.virgo.ide.internal.utils.json,
diff --git a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/core/IServerBehaviour.java b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/core/IServerBehaviour.java
index c37ee9f..b685c20 100644
--- a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/core/IServerBehaviour.java
+++ b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/core/IServerBehaviour.java
@@ -58,4 +58,10 @@
 
     void onModulePublishStateChange(IModule[] modules, int publishStateNone);
 
+    /**
+     * Returns the server deploy directory. This corresponds to the stage folder created
+     * by the Virgo Tools within the Virgo home folder.
+     */
+    public IPath getServerDeployDirectory();
+
 }
diff --git a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/ServerPublishOperation.java b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/ServerPublishOperation.java
index 630c079..b8fb6f9 100644
--- a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/ServerPublishOperation.java
+++ b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/ServerPublishOperation.java
@@ -83,6 +83,7 @@
 
         boolean shouldReployChild = false;
         IProject project = this.modules[0].getProject();
+        // not a bundle, not a par, not a plan -> a WST Web Project
         if (!FacetUtils.isBundleProject(project) && !FacetUtils.isParProject(project)
             && ServerUtils.getServer(this.server).getChildModules(this.modules) != null && !FacetUtils.isPlanProject(project)) {
             for (IModule module : ServerUtils.getServer(this.server).getChildModules(this.modules)) {
@@ -110,42 +111,6 @@
     }
 
     /**
-     * Checks if the given <code>file</code> is a root node that is a known Spring namespace.
-     */
-    // private boolean checkIfSpringConfigurationFile(IFile file) {
-    // IStructuredModel model = null;
-    // try {
-    // model =
-    // StructuredModelManager.getModelManager().getExistingModelForRead(file);
-    // if (model == null) {
-    // model = StructuredModelManager.getModelManager().getModelForRead(file);
-    // }
-    // if (model != null) {
-    // IDOMDocument document = ((DOMModelImpl) model).getDocument();
-    // if (document != null && document.getDocumentElement() != null) {
-    // String namespaceUri = document.getDocumentElement().getNamespaceURI();
-    // if (NamespaceUtils.DEFAULT_NAMESPACE_URI.equals(namespaceUri)
-    // || new
-    // DelegatingNamespaceHandlerResolver(JdtUtils.getClassLoader(file.getProject(),
-    // null),
-    // null).resolve(namespaceUri) != null) {
-    // return false;
-    // }
-    // }
-    // }
-    // }
-    // catch (Exception e) {
-    // }
-    // finally {
-    // if (model != null) {
-    // model.releaseFromRead();
-    // }
-    // model = null;
-    // }
-    // return true;
-    // }
-
-    /**
      * Check if resource delta only contains static resources
      */
     private boolean onlyStaticResources(IModuleResourceDelta delta, Set<IModuleFile> files) {
diff --git a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/command/JmxServerDeployCommand.java b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/command/JmxServerDeployCommand.java
index 48437fe..ac37675 100644
--- a/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/command/JmxServerDeployCommand.java
+++ b/org.eclipse.virgo.ide.runtime.core/src/org/eclipse/virgo/ide/runtime/internal/core/command/JmxServerDeployCommand.java
@@ -11,18 +11,26 @@
 
 package org.eclipse.virgo.ide.runtime.internal.core.command;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
 
 import javax.management.openmbean.CompositeData;
 
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.virgo.ide.facet.core.FacetCorePlugin;
+import org.eclipse.virgo.ide.facet.core.FacetUtils;
 import org.eclipse.virgo.ide.runtime.core.IServerBehaviour;
 import org.eclipse.virgo.ide.runtime.core.ServerUtils;
 import org.eclipse.virgo.ide.runtime.internal.core.DeploymentIdentity;
 import org.eclipse.virgo.ide.runtime.internal.core.Server;
+import org.eclipse.virgo.util.io.FileCopyUtils;
 import org.eclipse.wst.server.core.IModule;
 
 /**
@@ -31,7 +39,7 @@
  * @author Christian Dupuis
  * @since 1.0.1
  */
-public class JmxServerDeployCommand extends AbstractJmxServerDeployerCommand<CompositeData>implements IServerCommand<DeploymentIdentity> {
+public class JmxServerDeployCommand extends AbstractJmxServerDeployerCommand<CompositeData> implements IServerCommand<DeploymentIdentity> {
 
     private static final String ITEM_SYMBOLIC_NAME = "symbolicName"; //$NON-NLS-1$
 
@@ -71,6 +79,27 @@
             }
         }
 
+        if (isPlan()) {
+            // plan module name is workspace-relative path
+            String path = module.getName();
+            IFile planFile = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path));
+
+            List<IFile> files = FacetUtils.getNestedPlanFiles(planFile);
+            IPath stageDir = this.serverBehaviour.getServerDeployDirectory();
+            File stageFileDir = stageDir.toFile();
+
+            // make sure nested plans, if any, are copied to the stage dir so that
+            // they can be found
+            for (IFile iFile : files) {
+                File oldFile = new File(stageFileDir, iFile.getName());
+                if (oldFile.exists()) {
+                    oldFile.delete();
+                }
+                FileCopyUtils.copy(iFile.getLocation().toFile(), oldFile);
+            }
+
+        }
+
         CompositeData returnValue = doExecute();
         if (returnValue != null) {
             String symbolicName = (String) returnValue.get(ITEM_SYMBOLIC_NAME);
@@ -88,7 +117,7 @@
     @Override
     protected Object[] getOperationArguments() {
         URI uri = null;
-        if (this.module.getModuleType().getId().equals(FacetCorePlugin.PLAN_FACET_ID)) {
+        if (isPlan()) {
             String fileName = this.module.getId();
             fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
             uri = getUri(this.serverBehaviour.getModuleDeployUri(this.module).append(fileName));
@@ -98,6 +127,10 @@
         return new Object[] { uri.toString(), false };
     }
 
+    private boolean isPlan() {
+        return this.module.getModuleType().getId().equals(FacetCorePlugin.PLAN_FACET_ID);
+    }
+
     /**
      * {@inheritDoc}
      */