blob: a6dce3c85782f95b33b1fcd7947d41898b741f82 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2014 Sonatype, 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
*******************************************************************************/
package org.eclipse.m2e.wtp.internal.filtering;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.Scanner;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomUtils;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.internal.MavenPluginActivator;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.core.project.ResolverConfiguration;
import org.eclipse.m2e.core.project.configurator.AbstractBuildParticipant;
import org.eclipse.m2e.wtp.DomUtils;
import org.eclipse.m2e.wtp.MavenWtpConstants;
import org.eclipse.m2e.wtp.WTPProjectsUtil;
import org.eclipse.m2e.wtp.internal.Messages;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.build.incremental.BuildContext;
import org.sonatype.plexus.build.incremental.ThreadBuildContext;
/**
* ResourceFilteringBuildParticipant
*
* @author Fred Bricon
*/
@SuppressWarnings("restriction")
public class ResourceFilteringBuildParticipant extends AbstractBuildParticipant {
private static final Logger LOG = LoggerFactory.getLogger(ResourceFilteringBuildParticipant.class );
private CleanBuildContext forceCopyBuildContext;
@Override
public Set<IProject> build(int kind, IProgressMonitor monitor) throws Exception {
IMavenProjectFacade facade = getMavenProjectFacade();
ResourceFilteringConfiguration configuration = ResourceFilteringConfigurationFactory.getConfiguration(facade);
List<Xpp3Dom> resources = null;
if (configuration == null || (resources = configuration.getResources()) == null) {
//Nothing to filter
return null;
}
IProject project = facade.getProject();
//FIXME assuming path relative to current project
IPath targetFolder = configuration.getTargetFolder();
IResourceDelta delta = getDelta(project);
BuildContext oldBuildContext = ThreadBuildContext.getContext();
try {
forceCopyBuildContext = null;
List<String> filters = configuration.getFilters();
if (!project.getFolder(targetFolder).exists() || changeRequiresForcedCopy(facade, filters, delta)) {
LOG.info(NLS.bind(Messages.ResourceFilteringBuildParticipant_Changed_Resources_Require_Clean_Build,project.getName()));
//String id = "" + "-" + getClass().getName();
forceCopyBuildContext = new CleanBuildContext(oldBuildContext);
ThreadBuildContext.setThreadBuildContext(forceCopyBuildContext);
}
if (forceCopyBuildContext != null || hasResourcesChanged(facade, delta, resources)) {
LOG.info(NLS.bind(Messages.ResourceFilteringBuildParticipant_Executing_Resource_Filtering,project.getName()));
executeCopyResources(facade, configuration, targetFolder, resources, monitor);
//FIXME deal with absolute paths
IFolder destFolder = project.getFolder(targetFolder);
if (destFolder.exists()){
destFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
}
} finally {
ThreadBuildContext.setThreadBuildContext(oldBuildContext);
}
return null;
}
@Override
protected BuildContext getBuildContext() {
return (forceCopyBuildContext == null)?super.getBuildContext() : forceCopyBuildContext;
}
/**
* If the pom.xml or any of the project's filters were changed, a forced copy is required
* @param facade
* @param delta
* @return
*/
private boolean changeRequiresForcedCopy(IMavenProjectFacade facade, List<String> filters, IResourceDelta delta) {
if (delta == null) {
return false;
}
if (delta.findMember(facade.getPom().getProjectRelativePath()) != null ) {
return true;
}
for (String filter : filters) {
IPath filterPath = facade.getProjectRelativePath(filter);
if (filterPath == null) {
filterPath =Path.fromOSString(filter);
}
if (delta.findMember(filterPath) != null){
return true;
}
}
return false;
}
@Override
public void clean(IProgressMonitor monitor) throws CoreException {
IMavenProjectFacade facade = getMavenProjectFacade();
ResourceFilteringConfiguration configuration = ResourceFilteringConfigurationFactory.getConfiguration(facade);
if (configuration == null) {
//Nothing to do
return;
}
IProject project = facade.getProject();
IPath targetFolderPath = configuration.getTargetFolder();
deleteFilteredResources(project, targetFolderPath);
super.clean(monitor);
}
private void deleteFilteredResources(IProject project, IPath targetFolderPath) throws CoreException {
IFolder targetFolder = project.getFolder(targetFolderPath);
if (targetFolder.exists()) {
IContainer parent = targetFolder.getParent();
LOG.info(NLS.bind(Messages.ResourceFilteringBuildParticipant_Cleaning_Filtered_Folder,project.getName()));
IProgressMonitor monitor =new NullProgressMonitor();
targetFolder.delete(true, monitor);
if (parent != null) {
parent.refreshLocal(IResource.DEPTH_INFINITE, monitor );
}
}
}
/**
* @param mavenProject
* @param iResourceDelta
* @param resources
* @return
*/
private boolean hasResourcesChanged(IMavenProjectFacade facade, IResourceDelta delta, List<Xpp3Dom> resources) {
if (resources == null || resources.isEmpty()){
return false;
}
Set<IPath> resourcePaths = getResourcePaths(facade, resources);
if(delta == null) {
return !resourcePaths.isEmpty();
}
for(IPath resourcePath : resourcePaths) {
IResourceDelta member = delta.findMember(resourcePath);
//XXX deal with member kind/flags
if(member != null) {
return true;
//we need to deal with superceded resources on the maven level
}
}
return false;
}
private Set<IPath> getResourcePaths(IMavenProjectFacade facade, List<Xpp3Dom> resources) {
Set<IPath> resourcePaths = new LinkedHashSet<>();
for(Xpp3Dom resource : resources) {
IPath folder= null;
Xpp3Dom xpp3Directory = resource.getChild("directory"); //$NON-NLS-1$
if (xpp3Directory != null)
{
String dir = xpp3Directory.getValue();
if (StringUtils.isNotEmpty(dir)){
folder = WTPProjectsUtil.tryProjectRelativePath(facade.getProject(), dir);
}
}
if(folder != null && !folder.isEmpty()) {
resourcePaths.add(folder);
}
}
return resourcePaths;
}
private void executeCopyResources(IMavenProjectFacade facade, ResourceFilteringConfiguration filteringConfiguration, IPath targetFolder, List<Xpp3Dom> resources, IProgressMonitor monitor) throws CoreException {
//Create a maven request + session
ResolverConfiguration resolverConfig = facade.getResolverConfiguration();
List<String> filters = filteringConfiguration.getFilters();
IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry();
MavenExecutionRequest request = projectManager.createExecutionRequest(facade.getPom(), resolverConfig, monitor);
request.setRecursive(false);
request.setOffline(true);
IMaven maven = MavenPlugin.getMaven();
MavenProject mavenProject = facade.getMavenProject();
MavenSession session = maven.createSession(request, mavenProject);
MavenExecutionPlan executionPlan = maven.calculateExecutionPlan(session, mavenProject, Collections.singletonList("resources:copy-resources"), true, monitor); //$NON-NLS-1$
MojoExecution copyFilteredResourcesMojo = getExecution(executionPlan, "maven-resources-plugin"); //$NON-NLS-1$
if (copyFilteredResourcesMojo == null) return;
Xpp3Dom originalConfig = copyFilteredResourcesMojo.getConfiguration();
Xpp3Dom configuration = Xpp3DomUtils.mergeXpp3Dom(new Xpp3Dom("configuration"), originalConfig); //$NON-NLS-1$
boolean parentHierarchyLoaded = false;
try {
parentHierarchyLoaded = loadParentHierarchy(facade, monitor);
//Set resource directories to read
setupResources(configuration, resources);
//Force overwrite
setValue(configuration, "overwrite", Boolean.TRUE); //$NON-NLS-1$
//Limit placeholder delimiters, otherwise, pages containing @ wouldn't be filtered correctly
setupDelimiters(configuration);
//Set output directory to the m2eclipse-wtp webresources directory
setValue(configuration, "outputDirectory", targetFolder.toPortableString()); //$NON-NLS-1$
setValue(configuration, "escapeString", filteringConfiguration.getEscapeString()); //$NON-NLS-1$
setNonfilteredExtensions(configuration, filteringConfiguration.getNonfilteredExtensions());
//Setup filters
setupFilters(configuration, filters);
//Create a maven request + session
request.setRecursive(false);
request.setOffline(true);
//Execute our modified mojo
copyFilteredResourcesMojo.setConfiguration(configuration);
copyFilteredResourcesMojo.getMojoDescriptor().setGoal("copy-resources"); //$NON-NLS-1$
maven.execute(session, copyFilteredResourcesMojo, monitor);
if (session.getResult().hasExceptions()){
MavenPluginActivator.getDefault().getMavenMarkerManager().addMarker(facade.getProject(), MavenWtpConstants.WTP_MARKER_FILTERING_ERROR,Messages.ResourceFilteringBuildParticipant_Error_While_Filtering_Resources, -1, IMarker.SEVERITY_ERROR);
//move exceptions up to the original session, so they can be handled by the maven builder
//XXX current exceptions refer to maven-resource-plugin (since that's what we used), we should probably
// throw a new exception instead to indicate the problem(s) come(s) from web resource filtering
for(Throwable t : session.getResult().getExceptions())
{
getSession().getResult().addException(t);
}
}
} finally {
//Restore original configuration
copyFilteredResourcesMojo.setConfiguration(originalConfig);
if (parentHierarchyLoaded) {
mavenProject.setParent(null);
}
}
}
/**
* @param configuration
* @param extensions
*/
private void setNonfilteredExtensions(Xpp3Dom configuration, List<Xpp3Dom> extensions) {
if (extensions == null || extensions.isEmpty()) {
return;
}
Xpp3Dom nonFilteredFileExtensionsNode = configuration.getChild("nonFilteredFileExtensions"); //$NON-NLS-1$
if (nonFilteredFileExtensionsNode == null) {
nonFilteredFileExtensionsNode = new Xpp3Dom("nonFilteredFileExtensions"); //$NON-NLS-1$
configuration.addChild(nonFilteredFileExtensionsNode);
} else {
DomUtils.removeChildren(nonFilteredFileExtensionsNode);
}
for (Xpp3Dom ext : extensions) {
nonFilteredFileExtensionsNode.addChild(ext);
}
}
private void setValue(Xpp3Dom configuration, String childName, Object value) {
Xpp3Dom childNode = configuration.getChild(childName);
if (childNode==null){
childNode = new Xpp3Dom(childName);
configuration.addChild(childNode);
}
childNode.setValue((value == null)?null:value.toString());
}
private void setupFilters(Xpp3Dom configuration, List<String> filters) {
if (!filters.isEmpty()) {
Xpp3Dom filtersNode = configuration.getChild("filters"); //$NON-NLS-1$
if (filtersNode==null){
filtersNode = new Xpp3Dom("filters"); //$NON-NLS-1$
configuration.addChild(filtersNode);
} else {
DomUtils.removeChildren(filtersNode);
}
for (String filter : filters) {
Xpp3Dom filterNode = new Xpp3Dom("filter"); //$NON-NLS-1$
//Workaround : when run via the BuildParticipant, the maven-resource-plugin won't
//find a filter defined with a relative path, so we turn it into an absolute one
IPath filterPath = new Path(filter);
boolean isAbsolute = false;
if (filter.startsWith("${basedir}") ||filter.startsWith("/") || filterPath.getDevice() != null) { //$NON-NLS-1$ //$NON-NLS-2$
isAbsolute = true;
}
String filterAbsolutePath;
if (isAbsolute) {
filterAbsolutePath = filter;
} else {
filterAbsolutePath = "${basedir}/"+filter; //$NON-NLS-1$
}
filterNode.setValue(filterAbsolutePath);
filtersNode.addChild(filterNode );
}
}
}
private void setupDelimiters(Xpp3Dom configuration) {
Xpp3Dom useDefaultDelimitersNode = configuration.getChild("useDefaultDelimiters"); //$NON-NLS-1$
if (useDefaultDelimitersNode==null){
useDefaultDelimitersNode = new Xpp3Dom("useDefaultDelimiters"); //$NON-NLS-1$
configuration.addChild(useDefaultDelimitersNode);
}
useDefaultDelimitersNode.setValue(Boolean.FALSE.toString());
Xpp3Dom delimitersNode = configuration.getChild("delimiters"); //$NON-NLS-1$
if (delimitersNode==null){
delimitersNode = new Xpp3Dom("delimiters"); //$NON-NLS-1$
configuration.addChild(delimitersNode);
} else {
DomUtils.removeChildren(delimitersNode);
}
Xpp3Dom delimiter = new Xpp3Dom("delimiter"); //$NON-NLS-1$
delimiter.setValue("${*}"); //$NON-NLS-1$
delimitersNode.addChild(delimiter);
}
private void setupResources(Xpp3Dom configuration, List<Xpp3Dom> resources) {
Xpp3Dom resourcesNode = configuration.getChild("resources"); //$NON-NLS-1$
if (resourcesNode==null){
resourcesNode = new Xpp3Dom("resources"); //$NON-NLS-1$
configuration.addChild(resourcesNode);
} else {
resourcesNode.setAttribute("default-value", ""); //$NON-NLS-1$ //$NON-NLS-2$
DomUtils.removeChildren(resourcesNode);
}
for (Xpp3Dom resource : resources)
{
resourcesNode.addChild(resource);
}
}
private MojoExecution getExecution(MavenExecutionPlan executionPlan, String artifactId) {
if (executionPlan == null) return null;
for(MojoExecution execution : executionPlan.getMojoExecutions()) {
if(artifactId.equals(execution.getArtifactId()) ) {
return execution;
}
}
return null;
}
/**
* Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=356725.
* Loads the parent project hierarchy if needed.
* @param facade
* @param monitor
* @return true if parent projects had to be loaded.
* @throws CoreException
*/
private boolean loadParentHierarchy(IMavenProjectFacade facade, IProgressMonitor monitor) throws CoreException {
boolean loadedParent = false;
MavenProject mavenProject = facade.getMavenProject();
try {
if (mavenProject.getModel().getParent() == null || mavenProject.getParent() != null) {
//If the method is called without error, we can assume the project has been fully loaded
//No need to continue.
return false;
}
} catch (IllegalStateException e) {
//The parent can not be loaded properly
}
MavenExecutionRequest request = null;
while(mavenProject !=null && mavenProject.getModel().getParent() != null) {
if(monitor.isCanceled()) {
break;
}
if (request == null) {
request = MavenPlugin.getMavenProjectRegistry().createExecutionRequest(facade, monitor);
}
MavenProject parentProject = MavenPlugin.getMaven().resolveParentProject(request, mavenProject, monitor);
if (parentProject != null) {
mavenProject.setParent(parentProject);
loadedParent = true;
}
mavenProject = parentProject;
}
return loadedParent;
}
private static class CleanBuildContext implements BuildContext {
private BuildContext originalContext;
CleanBuildContext(BuildContext originalContext) {
this.originalContext = originalContext;
}
public boolean hasDelta(String relpath) {
return true;
}
public boolean hasDelta(File file) {
return true;
}
public boolean hasDelta(List relpaths) {
return true;
}
public void refresh(File file) {
originalContext.refresh(file);
}
public OutputStream newFileOutputStream(File file) throws IOException {
return originalContext.newFileOutputStream(file);
}
public Scanner newScanner(File basedir) {
return originalContext.newScanner(basedir);
}
public Scanner newDeleteScanner(File basedir) {
return originalContext.newDeleteScanner(basedir);
}
public Scanner newScanner(File basedir, boolean ignoreDelta) {
return originalContext.newScanner(basedir, ignoreDelta);
}
public boolean isIncremental() {
return false;
}
public void setValue(String key, Object value) {
originalContext.setValue(key, value);
}
public Object getValue(String key) {
return originalContext.getValue(key);
}
public void addWarning(File file, int line, int column, String message,
Throwable cause) {
originalContext.addWarning(file, line, column, message, cause);
}
public void addError(File file, int line, int column, String message,
Throwable cause) {
originalContext.addError(file, line, column, message, cause);
}
public void addMessage(File file, int line, int column, String message,
int severity, Throwable cause) {
originalContext.addMessage(file, line, column, message, severity, cause);
}
public void removeMessages(File file) {
originalContext.removeMessages(file);
}
public boolean isUptodate(File target, File source) {
return false;
}
}
}