/******************************************************************************* * Copyright (c) 2009, 2010 SpringSource, a divison of VMware, Inc. * 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: * SpringSource, a division of VMware, Inc. - initial API and implementation * GianMaria Romanato - support for nested plans *******************************************************************************/ package org.eclipse.virgo.ide.module.core; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.lang.ObjectUtils; 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.IWorkspaceRoot; 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; import org.eclipse.jdt.core.IClasspathAttribute; 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.core.FacetCorePlugin; import org.eclipse.virgo.ide.facet.core.FacetUtils; import org.eclipse.virgo.ide.manifest.core.BundleManifestCorePlugin; import org.eclipse.virgo.ide.par.Bundle; import org.eclipse.virgo.ide.par.Par; import org.eclipse.virgo.util.osgi.manifest.BundleManifest; import org.eclipse.wst.server.core.IModule; import org.eclipse.wst.server.core.ServerUtil; import org.eclipse.wst.server.core.model.IModuleFile; import org.eclipse.wst.server.core.model.IModuleFolder; import org.eclipse.wst.server.core.model.IModuleResource; import org.eclipse.wst.server.core.model.ModuleDelegate; import org.eclipse.wst.server.core.util.ModuleFile; import org.eclipse.wst.server.core.util.ModuleFolder; import org.eclipse.wst.server.core.util.ProjectModule; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * {@link ProjectModule} extension that knows how to handle par and bundle projects. * * @author Christian Dupuis * @author Terry Hon * @since 1.0.0 */ @SuppressWarnings({ "deprecation", "restriction" }) public class ServerModuleDelegate extends ProjectModule { /** Make */ public static final String TEST_CLASSPATH_ENTRY_ATTRIBUTE = "org.eclipse.virgo.ide.jdt.core.test.classpathentry"; public ServerModuleDelegate(IProject project) { super(project); } @Override public IModuleResource[] members() throws CoreException { IModule module = getModule(); final Set<IModuleResource> resources = new LinkedHashSet<IModuleResource>(); /* * Add recursion to collect nested elements. This method now returns as members of toplevel plans all nested * plans and bundles collected via recursion. * * Originally it was returning only direct children and assuming they were bundles, which made it impossible for * the tools to deploy nested plans. * * Note that when top level plans with nested plans are added to the virgo runtime, they are added as a tree of * IModule objects. However, apparently WTP does not properly deal with arbitrary nesting of modules and just * publishes resources for the top level modules and their direct children. * * As such to overcome this limitation the following code fools WTP by providing as resources of the top level * plan all resources contained in nested plans and nested bundles, even if they are represented by different * IModule instances in memory. */ deepGetMembers(module, resources); return resources.toArray(new IModuleResource[resources.size()]); } private void deepGetMembers(IModule module, final Set<IModuleResource> resources) throws CoreException, JavaModelException { IPath moduleRelativePath = Path.EMPTY; // Handle simple case of project being a bundle first if (module.getModuleType().getId().equals(FacetCorePlugin.BUNDLE_FACET_ID)) { if (FacetUtils.hasProjectFacet(getProject(), FacetCorePlugin.WEB_FACET_ID)) { IModule[] modules = ServerUtil.getModules(getProject()); for (IModule webModule : modules) { if (webModule.getModuleType().getId().equals(FacetCorePlugin.WEB_FACET_ID)) { ModuleDelegate delegate = (ModuleDelegate) webModule.loadAdapter(ModuleDelegate.class, null); resources.addAll(Arrays.asList(delegate.members())); } } } resources.addAll(getMembers(getProject(), moduleRelativePath)); } // More complex handling of PAR and nested bundle project else if (module.getModuleType().getId().equals(FacetCorePlugin.PAR_FACET_ID)) { // Get the META-INF folder of the PAR first IResource metaInfFolder = getProject().findMember(BundleManifestCorePlugin.MANIFEST_FOLDER_NAME); if (metaInfFolder instanceof IContainer) { String moduleFolderName = BundleManifestCorePlugin.MANIFEST_FOLDER_NAME; moduleRelativePath = new Path(moduleFolderName); ModuleFolder folder = new ModuleFolder(null, moduleFolderName, Path.EMPTY); folder.setMembers(getModuleResources(moduleRelativePath, (IContainer) metaInfFolder)); resources.add(folder); } else { StatusManager.getManager().handle(new Status(IStatus.ERROR, BundleManifestCorePlugin.PLUGIN_ID, "Cannot find META-INF/MANIFEST.MF in project [" + getProject().getName() + "]")); } // Find linked or nested jars and add them to the deployment getProject().accept(new IResourceVisitor() { public boolean visit(IResource resource) throws CoreException { if (resource instanceof IFile && resource.getFileExtension().equals("jar")) { resources.add(new ModuleFile((IFile) resource, resource.getName(), Path.EMPTY)); } return true; } }, IResource.DEPTH_ONE, false); // Iterate nested bundle projects for (IModule childModule : getChildModules()) { // Special handling of par nested wars with bundle nature if (FacetUtils.hasProjectFacet(childModule.getProject(), FacetCorePlugin.WEB_FACET_ID)) { moduleRelativePath = new Path(childModule.getProject().getName() + ".war"); ModuleDelegate delegate = (ModuleDelegate) childModule.loadAdapter(ModuleDelegate.class, null); IModuleResource[] members = delegate.members(); for (IModuleResource member : members) { if (member instanceof IModuleFile) { resources.add(new ParModuleFile((IModuleFile) member, moduleRelativePath)); } else if (member instanceof IModuleFolder) { resources.add(new ParModuleFolder((IModuleFolder) member, moduleRelativePath)); } } } // All other bundles project nested in a par else if (FacetUtils.isBundleProject(childModule.getProject())) { String moduleFolderName = childModule.getProject().getName() + ".jar"; moduleRelativePath = new Path(moduleFolderName); ModuleFolder folder = new ModuleFolder(null, moduleFolderName, Path.EMPTY); folder.setMembers(getMembers(childModule.getProject(), moduleRelativePath).toArray(new IModuleResource[0])); resources.add(folder); } } } // handling for plan projects else if (module.getModuleType().getId().equals(FacetCorePlugin.PLAN_FACET_ID)) { // Get the plan file String fileName = module.getId(); fileName = fileName.substring(fileName.indexOf(':') + 1); IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fileName)); if (!file.exists()) { return; } ModuleFile planFile = new ModuleFile(file, file.getName(), moduleRelativePath); resources.add(planFile); // Iterate nested bundle projects ModuleDelegate delegate0 = (ModuleDelegate) module.loadAdapter(ModuleDelegate.class, null); for (IModule childModule : delegate0.getChildModules()) { // Special handling of par nested wars with bundle nature if (FacetUtils.hasProjectFacet(childModule.getProject(), FacetCorePlugin.WEB_FACET_ID)) { moduleRelativePath = new Path(childModule.getProject().getName() + ".war"); ModuleDelegate delegate1 = (ModuleDelegate) childModule.loadAdapter(ModuleDelegate.class, null); IModuleResource[] members = delegate1.members(); for (IModuleResource member : members) { if (member instanceof IModuleFile) { resources.add(new ParModuleFile((IModuleFile) member, moduleRelativePath)); } else if (member instanceof IModuleFolder) { resources.add(new ParModuleFolder((IModuleFolder) member, moduleRelativePath)); } } } // All other bundles project nested in a par else if (FacetUtils.isBundleProject(childModule.getProject())) { String moduleFolderName = childModule.getProject().getName() + ".jar"; moduleRelativePath = new Path(moduleFolderName); ModuleFolder folder = new ModuleFolder(null, moduleFolderName, Path.EMPTY); folder.setMembers(getMembers(childModule.getProject(), moduleRelativePath).toArray(new IModuleResource[0])); resources.add(folder); } else if (FacetUtils.isPlanProject(childModule.getProject())) { // enter recursion deepGetMembers(childModule, resources); } else if (FacetUtils.isParProject(childModule.getProject())) { moduleRelativePath = new Path(childModule.getProject().getName() + ".par"); ModuleDelegate delegate2 = (ModuleDelegate) childModule.loadAdapter(ModuleDelegate.class, null); IModuleResource[] members = delegate2.members(); for (IModuleResource member : members) { if (member instanceof IModuleFile) { resources.add(new ParModuleFile((IModuleFile) member, moduleRelativePath)); } else if (member instanceof IModuleFolder) { resources.add(new ParModuleFolder((IModuleFolder) member, moduleRelativePath)); } } } } } } /** * Get all resources from project's output locations */ private Set<IModuleResource> getMembers(IProject project, IPath moduleRelativePath) throws JavaModelException, CoreException { Set<IModuleResource> resources = new LinkedHashSet<IModuleResource>(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IJavaProject javaProject = JavaCore.create(project); // Add default output location IResource defaultBinFolder = root.findMember(javaProject.getOutputLocation()); if (defaultBinFolder instanceof IContainer) { resources.addAll(Arrays.asList(getModuleResources(moduleRelativePath, (IContainer) defaultBinFolder))); } // Add output for every source entry for (IClasspathEntry entry : getSourceClasspathEntries(project, false)) { IResource binFolder = root.findMember(entry.getOutputLocation()); if (binFolder instanceof IContainer && !(binFolder instanceof IWorkspaceRoot)) { resources.addAll(Arrays.asList(getModuleResources(moduleRelativePath, (IContainer) defaultBinFolder))); } } // Add Bundle-ClassPath entries BundleManifest manifest = BundleManifestCorePlugin.getBundleManifestManager().getBundleManifest(javaProject); if (manifest != null) { List<String> bundleClassPathEntries = manifest.getBundleClasspath(); if (bundleClassPathEntries != null) { // remove the . for the class folder from the bundle classpath entries bundleClassPathEntries.remove("."); // get all resources that match the given Bundle-ClassPath header resources.addAll(Arrays.asList(getModuleResources(moduleRelativePath, javaProject.getProject(), bundleClassPathEntries))); } } return resources; } /** * Gets all resources of the given <code>container</code> but filters against names given by <code>filters</code>. */ protected IModuleResource[] getModuleResources(IPath path, IContainer container, List<String> filters) throws CoreException { IResource[] resources = container.members(); if (resources != null) { int size = resources.length; List<IModuleResource> list = new ArrayList<IModuleResource>(size); for (int i = 0; i < size; i++) { IResource resource = resources[i]; if (resource != null && resource.exists()) { String name = resource.getName(); String relativePath = resource.getProjectRelativePath().toString(); if (resource instanceof IContainer) { for (String filter : filters) { if (filter.trim().startsWith(relativePath)) { IContainer container2 = (IContainer) resource; ModuleFolder mf = new org.eclipse.wst.server.core.internal.ModuleFolder(container2, name, path); mf.setMembers(getModuleResources(path.append(name), container2, filters)); list.add(mf); break; } } } else if (resource instanceof IFile) { for (String filter : filters) { if (relativePath.equals(filter.trim())) { list.add(new ModuleFile((IFile) resource, name, path)); break; } } } } } IModuleResource[] moduleResources = new IModuleResource[list.size()]; list.toArray(moduleResources); return moduleResources; } return new IModuleResource[0]; } /** * {@inheritDoc} */ @Override public IModule[] getChildModules() { if (FacetUtils.isParProject(getProject())) { Set<IModule> modules = new LinkedHashSet<IModule>(); Par par = FacetUtils.getParDefinition(getProject()); if (par != null && par.getBundle() != null) { for (Bundle bundle : par.getBundle()) { IProject bundleProject = ResourcesPlugin.getWorkspace().getRoot().getProject(bundle.getSymbolicName()); if (FacetUtils.isBundleProject(bundleProject)) { for (IModule module : ServerUtil.getModules(bundleProject)) { if (module.getId().equals( ServerModuleFactoryDelegate.MODULE_FACTORY_ID + ":" + getProject().getName() + "$" + bundleProject.getName())) { modules.add(module); } } } } } return modules.toArray(new IModule[modules.size()]); } else if (FacetUtils.isPlanProject(getProject())) { String fileName = getModule().getId(); fileName = fileName.substring(fileName.indexOf(':') + 1); IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fileName)); if (!file.exists()) { return new IModule[0]; } return getPlanDependencies(file).toArray(new IModule[0]); } return new IModule[0]; } /** * {@link IModuleFile} implementation that wraps another {@link IModuleFile} but moves the relative path into a sub * directory of a nested par module. */ static class ParModuleFile implements IModuleFile { private final IModuleFile wrappedFile; private final IPath modulePath; public ParModuleFile(IModuleFile wrappedFile, IPath modulePath) { this.wrappedFile = wrappedFile; this.modulePath = modulePath; } public long getModificationStamp() { return this.wrappedFile.getModificationStamp(); } public IPath getModuleRelativePath() { return this.modulePath.append(this.wrappedFile.getModuleRelativePath()); } public String getName() { return this.wrappedFile.getName(); } @SuppressWarnings("unchecked") public Object getAdapter(Class adapter) { return this.wrappedFile.getAdapter(adapter); } @Override public int hashCode() { return this.modulePath.hashCode() * 37 + this.wrappedFile.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof ParModuleFile)) { return false; } ParModuleFile other = (ParModuleFile) obj; if (!ObjectUtils.equals(this.modulePath, other.modulePath)) { return false; } return ObjectUtils.equals(this.wrappedFile, other.wrappedFile); } @Override public String toString() { return "ModuleFile [" + this.modulePath + "/" + this.wrappedFile.getModuleRelativePath() + ", " + this.wrappedFile.getName() + ", " + this.wrappedFile.getModificationStamp() + "]"; } } /** * {@link IModuleFolder} implementation that wraps another {@link IModuleFolder} but moves the relative path into a * sub directory of a nested par module. * * @see ParModuleFolder#members() */ static class ParModuleFolder implements IModuleFolder { private final IModuleFolder wrappedFolder; private final IPath modulePath; public ParModuleFolder(IModuleFolder wrappedFolder, IPath modulePath) { this.wrappedFolder = wrappedFolder; this.modulePath = modulePath; } public IModuleResource[] members() { Set<IModuleResource> members = new LinkedHashSet<IModuleResource>(); for (IModuleResource resource : this.wrappedFolder.members()) { if (resource instanceof IModuleFile) { members.add(new ParModuleFile((IModuleFile) resource, this.modulePath)); } else if (resource instanceof IModuleFolder) { members.add(new ParModuleFolder((IModuleFolder) resource, this.modulePath)); } } return members.toArray(new IModuleResource[members.size()]); } public IPath getModuleRelativePath() { return this.modulePath.append(this.wrappedFolder.getModuleRelativePath()); } public String getName() { return this.wrappedFolder.getName(); } @SuppressWarnings("unchecked") public Object getAdapter(Class adapter) { return this.wrappedFolder.getAdapter(adapter); } @Override public int hashCode() { return this.modulePath.hashCode() * 37 + this.wrappedFolder.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof ParModuleFolder)) { return false; } ParModuleFolder other = (ParModuleFolder) obj; if (!ObjectUtils.equals(this.modulePath, other.modulePath)) { return false; } return ObjectUtils.equals(this.wrappedFolder, other.wrappedFolder); } @Override public String toString() { return "ModuleFile [" + this.modulePath + "/" + this.wrappedFolder.getModuleRelativePath() + ", " + this.wrappedFolder.getName() + "]"; } } public static Set<IClasspathEntry> getSourceClasspathEntries(IProject project, boolean onlyTestFolders) { IJavaProject javaProject = JavaCore.create(project); if (javaProject == null) { return Collections.emptySet(); } Set<IClasspathEntry> entries = new LinkedHashSet<IClasspathEntry>(); try { for (IClasspathEntry entry : javaProject.getRawClasspath()) { if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { if (onlyTestFolders && !isSourceFolder(entry.getExtraAttributes()) || !onlyTestFolders && isSourceFolder(entry.getExtraAttributes())) { entries.add(entry); } } } } catch (JavaModelException e) { } return entries; } private static boolean isSourceFolder(IClasspathAttribute[] extraAttributes) { for (IClasspathAttribute attribute : extraAttributes) { if (TEST_CLASSPATH_ENTRY_ATTRIBUTE.equals(attribute.getName())) { return !Boolean.valueOf(attribute.getValue()); } } return true; } private Set<IModule> getPlanDependencies(IFile file) { if (file == null || !file.exists()) { return Collections.emptySet(); } Set<IModule> modules = new HashSet<IModule>(); /* add recursion to collect nested plans */ getPlanDependencies0(file, modules); return modules; } private void getPlanDependencies0(IFile file, Set<IModule> modules) { if (file == null || !file.exists()) { return; } try { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(file.getContents(true)); NodeList artifactNodes = doc.getDocumentElement().getElementsByTagName("artifact"); for (int i = 0; i < artifactNodes.getLength(); i++) { Element artifact = (Element) artifactNodes.item(i); String type = artifact.getAttribute("type"); String name = artifact.getAttribute("name"); if ("bundle".equals(type)) { IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (IProject candidate : projects) { if (FacetUtils.isBundleProject(candidate) || FacetUtils.hasProjectFacet(candidate, FacetCorePlugin.WEB_FACET_ID)) { BundleManifest manifest = BundleManifestCorePlugin.getBundleManifestManager().getBundleManifest( JavaCore.create(candidate)); if (manifest != null && manifest.getBundleSymbolicName() != null && manifest.getBundleSymbolicName().getSymbolicName() != null && manifest.getBundleSymbolicName().getSymbolicName().equals(name) || candidate.getName().equals(name)) { for (IModule module : ServerUtil.getModules(candidate)) { if (!module.getId().contains("$")) { modules.add(module); break; } } } } } } else if ("plan".equals(type)) { List<IFile> nested = FacetUtils.getNestedPlanFiles(file, false); for (IFile iFile : nested) { IModule[] mmmm = ServerUtil.getModules(iFile.getProject()); for (IModule iModule : mmmm) { String fileName = iModule.getId(); fileName = fileName.substring(fileName.indexOf(':') + 1); IFile file2 = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fileName)); if (iFile.equals(file2)) { modules.add(iModule); break; } } } } else if ("par".equals(type)) { IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (IProject candidate : projects) { if (FacetUtils.isParProject(candidate)) { if (candidate.getName().equals(name)) { for (IModule module : ServerUtil.getModules(candidate)) { modules.add(module); break; } } } } } } } catch (SAXException e) { StatusManager.getManager().handle( new Status(IStatus.ERROR, "Problem while getting plan dependencies.", BundleManifestCorePlugin.PLUGIN_ID, e)); } catch (IOException e) { StatusManager.getManager().handle( new Status(IStatus.ERROR, "Problem while getting plan dependencies.", BundleManifestCorePlugin.PLUGIN_ID, e)); } catch (CoreException e) { StatusManager.getManager().handle(e, BundleManifestCorePlugin.PLUGIN_ID); } catch (ParserConfigurationException e) { StatusManager.getManager().handle( new Status(IStatus.ERROR, "Problem while getting plan dependencies.", BundleManifestCorePlugin.PLUGIN_ID, e)); } } }