blob: b184a98bdf7864feba43697512e45f922888203d [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2009, 2010 SpringSource, a division of VMware, Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
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));
}
}
}