blob: df0586e952a6df3701a2c38f69c1ab26058c0956 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2013 Sonatype, Inc. and others.
* 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
*******************************************************************************/
package org.eclipse.m2e.wtp;
import static org.eclipse.m2e.wtp.WTPProjectsUtil.removeConflictingFacets;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jst.j2ee.classpathdep.IClasspathDependencyConstants;
import org.eclipse.jst.j2ee.internal.project.J2EEProjectUtilities;
import org.eclipse.jst.j2ee.project.facet.IJ2EEModuleFacetInstallDataModelProperties;
import org.eclipse.jst.j2ee.web.project.facet.IWebFacetInstallDataModelProperties;
import org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDataModelProvider;
import org.eclipse.jst.j2ee.web.project.facet.WebFacetUtils;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.jdt.IClasspathDescriptor;
import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
import org.eclipse.m2e.wtp.internal.ExtensionReader;
import org.eclipse.m2e.wtp.internal.Messages;
import org.eclipse.m2e.wtp.internal.filtering.WebResourceFilteringConfiguration;
import org.eclipse.m2e.wtp.internal.utilities.DebugUtilities;
import org.eclipse.m2e.wtp.namemapping.FileNameMapping;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.ModuleCoreNature;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IFacetedProject.Action;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configures web projects based on their maven-war-plugin configuration.
*
* @author Igor Fedorenko
* @author Fred Bricon
*/
@SuppressWarnings("restriction")
class WebProjectConfiguratorDelegate extends AbstractProjectConfiguratorDelegate {
private static final Logger LOG = LoggerFactory.getLogger(WebProjectConfiguratorDelegate.class);
/**
* See http://wiki.eclipse.org/ClasspathEntriesPublishExportSupport
*/
static final IClasspathAttribute DEPENDENCY_ATTRIBUTE = JavaCore.newClasspathAttribute(
IClasspathDependencyConstants.CLASSPATH_COMPONENT_DEPENDENCY, "/WEB-INF/lib"); //$NON-NLS-1$
private static final String CLASSPATH_ARCHIVENAME_ATTRIBUTE;
static {
String archiveNameAttribute = null;
try {
Field classpathArchiveNameField = IClasspathDependencyConstants.class.getField("CLASSPATH_ARCHIVENAME_ATTRIBUTE"); //$NON-NLS-1$
archiveNameAttribute = (String)classpathArchiveNameField.get(null);
} catch (Exception e) {
LOG.warn(Messages.WebProjectConfiguratorDelegate_Renamed_Dependencies_Will_Be_Copied);
}
CLASSPATH_ARCHIVENAME_ATTRIBUTE = archiveNameAttribute;
}
/**
* Name of maven property that overrides WTP context root.
*/
private static final String M2ECLIPSE_WTP_CONTEXT_ROOT = "m2eclipse.wtp.contextRoot"; //$NON-NLS-1$
@Override
protected void configure(IProject project, MavenProject mavenProject, IProgressMonitor monitor)
throws CoreException {
IFacetedProject facetedProject = ProjectFacetsManager.create(project, true, monitor);
IMavenProjectFacade facade = MavenPlugin.getMavenProjectRegistry().create(project.getFile(IMavenConstants.POM_FILE_NAME), true, monitor);
// make sure to update the main deployment folder
WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
String warSourceDirectory = config.getWarSourceDirectory();
IFolder contentFolder = project.getFolder(warSourceDirectory);
Set<Action> actions = new LinkedHashSet<Action>();
installJavaFacet(actions, project, facetedProject);
IVirtualComponent component = ComponentCore.createComponent(project, true);
//MNGECLIPSE-2279 get the context root from the final name of the project, or artifactId by default.
String contextRoot = getContextRoot(mavenProject, config.getWarName());
IProjectFacetVersion webFv = config.getWebFacetVersion(project);
IDataModel webModelCfg = getWebModelConfig(warSourceDirectory, contextRoot);
if(!facetedProject.hasProjectFacet(WebFacetUtils.WEB_FACET)) {
removeConflictingFacets(facetedProject, webFv, actions);
actions.add(new IFacetedProject.Action(IFacetedProject.Action.Type.INSTALL, webFv, webModelCfg));
} else {
IProjectFacetVersion projectFacetVersion = facetedProject.getProjectFacetVersion(WebFacetUtils.WEB_FACET);
if(webFv.getVersionString() != null && !webFv.getVersionString().equals(projectFacetVersion.getVersionString())){
actions.add(new IFacetedProject.Action(IFacetedProject.Action.Type.VERSION_CHANGE, webFv, webModelCfg));
}
}
String customWebXml = config.getCustomWebXml(project);
if(!actions.isEmpty()) {
ResourceCleaner fileCleaner = new ResourceCleaner(project, contentFolder);
try {
addFoldersToClean(fileCleaner, facade);
fileCleaner.addFiles(contentFolder.getFile("META-INF/MANIFEST.MF").getProjectRelativePath()); //$NON-NLS-1$
fileCleaner.addFolder(contentFolder.getFolder("WEB-INF/lib")); //$NON-NLS-1$
if (customWebXml != null) {
fileCleaner.addFiles(contentFolder.getFile("WEB-INF/web.xml").getProjectRelativePath()); //$NON-NLS-1$
}
facetedProject.modify(actions, monitor);
} finally {
//Remove any unwanted MANIFEST.MF the Facet installation has created
fileCleaner.cleanUp();
}
}
//MECLIPSEWTP-41 Fix the missing moduleCoreNature
fixMissingModuleCoreNature(project, monitor);
// MNGECLIPSE-632 remove test sources/resources from WEB-INF/classes
removeTestFolderLinks(project, mavenProject, monitor, "/WEB-INF/classes"); //$NON-NLS-1$
addContainerAttribute(project, DEPENDENCY_ATTRIBUTE, monitor);
//MNGECLIPSE-2279 change the context root if needed
if (!contextRoot.equals(J2EEProjectUtilities.getServerContextRoot(project))) {
J2EEProjectUtilities.setServerContextRoot(project, contextRoot);
}
if (customWebXml != null) {
linkFileFirst(project, customWebXml, "/WEB-INF/web.xml", monitor); //$NON-NLS-1$
}
component = ComponentCore.createComponent(project, true);
if(component != null) {
IPath warPath = new Path("/").append(contentFolder.getProjectRelativePath()); //$NON-NLS-1$
List<IPath> sourcePaths = new ArrayList<IPath>();
sourcePaths.add(warPath);
if (!WTPProjectsUtil.hasLink(project, ROOT_PATH, warPath, monitor)) {
component.getRootFolder().createLink(warPath, IVirtualResource.NONE, monitor);
}
//MECLIPSEWTP-22 support web filtered resources. Filtered resources directory must be declared BEFORE
//the regular web source directory. First resources discovered take precedence on deployment
IPath filteredFolder = new Path("/").append(WebResourceFilteringConfiguration.getTargetFolder(mavenProject, project)); //$NON-NLS-1$
boolean useBuildDir = MavenWtpPlugin.getDefault().getMavenWtpPreferencesManager().getPreferences(project).isWebMavenArchiverUsesBuildDirectory();
boolean useWebresourcefiltering = config.getWebResources() != null
&& config.getWebResources().length > 0
|| config.isFilteringDeploymentDescriptorsEnabled();
if (useBuildDir || useWebresourcefiltering) {
if (!useBuildDir && useWebresourcefiltering) {
mavenMarkerManager.addMarker(project, MavenWtpConstants.WTP_MARKER_CONFIGURATION_ERROR_ID,
Messages.markers_mavenarchiver_output_settings_ignored_warning, -1, IMarker.SEVERITY_WARNING);
}
sourcePaths.add(filteredFolder);
WTPProjectsUtil.insertLinkBefore(project, filteredFolder, warPath, new Path("/"), monitor); //$NON-NLS-1$
} else {
component.getRootFolder().removeLink(filteredFolder,IVirtualResource.NONE, monitor);
}
WTPProjectsUtil.deleteLinks(project, ROOT_PATH, sourcePaths, monitor);
WTPProjectsUtil.setDefaultDeploymentDescriptorFolder(component.getRootFolder(), warPath, monitor);
addComponentExclusionPatterns(component, config);
}
setModuleDependencies(project, mavenProject, monitor);
WTPProjectsUtil.removeWTPClasspathContainer(project);
}
private IDataModel getWebModelConfig(String warSourceDirectory, String contextRoot) {
IDataModel webModelCfg = DataModelFactory.createDataModel(new WebFacetInstallDataModelProvider());
webModelCfg.setProperty(IJ2EEModuleFacetInstallDataModelProperties.CONFIG_FOLDER, warSourceDirectory);
webModelCfg.setProperty(IWebFacetInstallDataModelProperties.CONTEXT_ROOT, contextRoot);
webModelCfg.setProperty(IJ2EEModuleFacetInstallDataModelProperties.GENERATE_DD, false);
webModelCfg.setBooleanProperty(IWebFacetInstallDataModelProperties.ADD_TO_EAR, false);
return webModelCfg;
}
@Override
public void setModuleDependencies(IProject project, MavenProject mavenProject, IProgressMonitor monitor)
throws CoreException {
IVirtualComponent component = ComponentCore.createComponent(project);
//if the attempt to create dependencies happens before the project is actually created, abort.
//this will be created again when the project exists.
if(component == null){
return;
}
//MECLIPSEWTP-41 Fix the missing moduleCoreNature
fixMissingModuleCoreNature(project, monitor);
DebugUtilities.debug("==============Processing "+project.getName()+" dependencies ==============="); //$NON-NLS-1$ //$NON-NLS-2$
WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
IPackagingConfiguration opts = new PackagingConfiguration(config.getPackagingIncludes(), config.getPackagingExcludes());
FileNameMapping fileNameMapping = config.getFileNameMapping();
List<AbstractDependencyConfigurator> depConfigurators = ExtensionReader.readDependencyConfiguratorExtensions(projectManager,
MavenPlugin.getMavenRuntimeManager(), mavenMarkerManager);
Set<IVirtualReference> references = new LinkedHashSet<IVirtualReference>();
List<IMavenProjectFacade> exportedDependencies = getWorkspaceDependencies(project, mavenProject);
Set<String> dups = new HashSet<String>();
Set<String> names = new HashSet<String>();
Map<IVirtualReference, Artifact> referenceMapping = new HashMap<IVirtualReference, Artifact>(exportedDependencies.size());
for(IMavenProjectFacade dependency : exportedDependencies) {
String depPackaging = dependency.getPackaging();
if ("pom".equals(depPackaging) //MNGECLIPSE-744 pom dependencies shouldn't be deployed //$NON-NLS-1$
|| "war".equals(depPackaging) //Overlays are dealt with the overlay configurator //$NON-NLS-1$
|| "zip".equals(depPackaging)) { //$NON-NLS-1$
continue;
}
try {
preConfigureDependencyProject(dependency, monitor);
if (!ModuleCoreNature.isFlexibleProject(dependency.getProject())) {
//Projects unsupported by WTP (ex. adobe flex projects) should not be added as references
continue;
}
MavenProject depMavenProject = dependency.getMavenProject(monitor);
IVirtualComponent depComponent = ComponentCore.createComponent(dependency.getProject());
ArtifactKey artifactKey = ArtifactHelper.toArtifactKey(depMavenProject.getArtifact());
//Get artifact using the proper classifier
Artifact artifact = ArtifactHelper.getArtifact(mavenProject.getArtifacts(), artifactKey);
if (artifact == null) {
//could not map key to artifact
artifact = depMavenProject.getArtifact();
}
ArtifactHelper.fixArtifactHandler(artifact.getArtifactHandler());
String deployedName = fileNameMapping.mapFileName(artifact);
boolean isDeployed = !artifact.isOptional() && opts.isPackaged("WEB-INF/lib/"+deployedName); //$NON-NLS-1$
//an artifact in mavenProject.getArtifacts() doesn't have the "optional" value as depMavenProject.getArtifact();
if (isDeployed) {
IVirtualReference reference = ComponentCore.createReference(component, depComponent);
IPath path = new Path("/WEB-INF/lib"); //$NON-NLS-1$
reference.setArchiveName(deployedName);
reference.setRuntimePath(path);
references.add(reference);
referenceMapping.put(reference, artifact);
if (!names.add(deployedName)) {
dups.add(deployedName);
}
}
} catch(RuntimeException ex) {
//Should probably be NPEs at this point
String dump = DebugUtilities.dumpProjectState("An error occured while configuring a dependency of "+project.getName()+DebugUtilities.SEP, dependency.getProject()); //$NON-NLS-1$
LOG.error(dump);
throw ex;
}
}
for (IVirtualReference reference : references) {
if (dups.contains(reference.getArchiveName())) {
Artifact a = referenceMapping.get(reference);
String newName = a.getGroupId() + "-" + reference.getArchiveName(); //$NON-NLS-1$
reference.setArchiveName(newName);
}
}
IVirtualReference[] oldRefs = WTPProjectsUtil.extractHardReferences(component, false);
IVirtualReference[] newRefs = references.toArray(new IVirtualReference[references.size()]);
if (WTPProjectsUtil.hasChanged(oldRefs, newRefs)){
//Only write in the .component file if necessary
IVirtualReference[] overlayRefs = WTPProjectsUtil.extractHardReferences(component, true);
IVirtualReference[] allRefs = new IVirtualReference[overlayRefs.length + newRefs.length];
System.arraycopy(newRefs, 0, allRefs, 0, newRefs.length);
System.arraycopy(overlayRefs, 0, allRefs, newRefs.length, overlayRefs.length);
component.setReferences(allRefs);
}
//TODO why a 2nd loop???
for(IMavenProjectFacade dependency : exportedDependencies) {
MavenProject depMavenProject = dependency.getMavenProject(monitor);
Iterator<AbstractDependencyConfigurator> configurators = depConfigurators.iterator();
while (configurators.hasNext()) {
try {
configurators.next().configureDependency(mavenProject, project, depMavenProject, dependency.getProject(), monitor);
} catch(MarkedException ex) {
//XXX handle this
}
}
}
}
/**
* Get the context root from a maven web project
* @param mavenProject
* @param warName
* @return the final name of the project if it exists, or the project's artifactId.
*/
protected String getContextRoot(MavenProject mavenProject, String warName) {
String contextRoot;
//MECLIPSEWTP-43 : Override with maven property
String property = mavenProject.getProperties().getProperty(M2ECLIPSE_WTP_CONTEXT_ROOT);
if (StringUtils.isBlank(property)) {
String finalName = warName;
if (StringUtils.isBlank(finalName)
|| finalName.equals(mavenProject.getArtifactId() + "-" + mavenProject.getVersion())) { //$NON-NLS-1$
contextRoot = mavenProject.getArtifactId();
} else {
contextRoot = finalName;
}
} else {
contextRoot = property;
}
return contextRoot.trim().replace(" ", "_"); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void configureClasspath(IProject project, MavenProject mavenProject, IClasspathDescriptor classpath,
IProgressMonitor monitor) throws CoreException {
//Improve skinny war support by generating the manifest classpath
//similar to mvn eclipse:eclipse
//http://maven.apache.org/plugins/maven-war-plugin/examples/skinny-wars.html
WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
IPackagingConfiguration opts = new PackagingConfiguration(config.getPackagingIncludes(), config.getPackagingExcludes());
/*
* Need to take care of three separate cases
*
* 1. remove any project dependencies (they are represented as J2EE module dependencies)
* 2. add non-dependency attribute for entries originated by artifacts with
* runtime, system, test scopes or optional dependencies (not sure about the last one)
* 3. make sure all dependency JAR files have unique file names, i.e. artifactId/version collisions
*/
Set<String> dups = new LinkedHashSet<String>();
Set<String> names = new HashSet<String>();
IVirtualComponent component = ComponentCore.createComponent(project);
if (component != null) {
for (IVirtualReference vr : component.getReferences()) {
if (!vr.getReferencedComponent().isBinary()) {
names.add(vr.getArchiveName());
}
}
}
FileNameMapping fileNameMapping = config.getFileNameMapping();
String targetDir = mavenProject.getBuild().getDirectory();
// first pass removes projects, adds non-dependency attribute and collects colliding filenames
Iterator<IClasspathEntryDescriptor> iter = classpath.getEntryDescriptors().iterator();
while (iter.hasNext()) {
IClasspathEntryDescriptor descriptor = iter.next();
String scope = descriptor.getScope();
Artifact artifact = ArtifactHelper.getArtifact(mavenProject.getArtifacts(), descriptor.getArtifactKey());
ArtifactHelper.fixArtifactHandler(artifact.getArtifactHandler());
String deployedName = fileNameMapping.mapFileName(artifact);
boolean isDeployed = (Artifact.SCOPE_COMPILE.equals(scope) || Artifact.SCOPE_RUNTIME.equals(scope))
&& !descriptor.isOptionalDependency()
&& opts.isPackaged("WEB-INF/lib/"+deployedName) //$NON-NLS-1$
&& !isWorkspaceProject(artifact);
// add non-dependency attribute if this classpathentry is not meant to be deployed
// or if it's a workspace project (projects already have a reference created in configure())
if(!isDeployed) {
descriptor.setClasspathAttribute(NONDEPENDENCY_ATTRIBUTE.getName(), NONDEPENDENCY_ATTRIBUTE.getValue());
//Bug #382078 : no need to rename non-deployed artifacts.
continue;
}
String fileName = descriptor.getPath().lastSegment();
if (!deployedName.equals(fileName)) {
if (CLASSPATH_ARCHIVENAME_ATTRIBUTE == null) {
//If custom fileName is used, check if the underlying file already exists
// if it doesn't, copy and rename the artifact under the build dir
IPath newPath = descriptor.getPath().removeLastSegments(1).append(deployedName);
if (!new File(newPath.toOSString()).exists()) {
newPath = renameArtifact(targetDir, descriptor.getPath(), deployedName );
}
if (newPath != null) {
descriptor.setPath(newPath);
}
} else {
descriptor.getClasspathAttributes().put(CLASSPATH_ARCHIVENAME_ATTRIBUTE, deployedName);
}
}
if (!names.add(deployedName)) {
dups.add(deployedName);
}
}
// second pass disambiguates colliding entry file names
iter = classpath.getEntryDescriptors().iterator();
while (iter.hasNext()) {
IClasspathEntryDescriptor descriptor = iter.next();
if (descriptor.getClasspathAttributes().containsKey(NONDEPENDENCY_ATTRIBUTE.getName())) {
//No need to rename if not deployed
continue;
}
if (dups.contains(descriptor.getPath().lastSegment())) {
String newName = descriptor.getGroupId() + "-" + descriptor.getPath().lastSegment(); //$NON-NLS-1$
if (CLASSPATH_ARCHIVENAME_ATTRIBUTE == null) {
IPath newPath = renameArtifact(targetDir, descriptor.getPath(), newName );
if (newPath != null) {
descriptor.setPath(newPath);
}
} else {
descriptor.getClasspathAttributes().put(CLASSPATH_ARCHIVENAME_ATTRIBUTE, newName);
}
}
}
}
@Deprecated
private IPath renameArtifact(String targetDir, IPath source, String newName) {
File src = new File(source.toOSString());
File dst = new File(targetDir, newName);
try {
if (src.isFile() && src.canRead()) {
if (isDifferent(src, dst)) { // uses lastModified
FileUtils.copyFile(src, dst);
dst.setLastModified(src.lastModified());
}
return Path.fromOSString(dst.getCanonicalPath());
}
} catch(IOException ex) {
LOG.error(Messages.WebProjectConfiguratorDelegate_File_Copy_Failed, ex);
}
return null;
}
private boolean isWorkspaceProject(Artifact artifact) {
IMavenProjectFacade facade = projectManager.getMavenProject(artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getVersion());
return facade != null
&& facade.getFullPath(artifact.getFile()) != null;
}
@Deprecated
private static boolean isDifferent(File src, File dst) {
if (!dst.exists()) {
return true;
}
return src.length() != dst.length()
|| src.lastModified() != dst.lastModified();
}
}