blob: 3905e00dd997d559eac5863c465d6edec84e2f50 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.core.internal.project.registry;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
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.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.embedder.ArtifactRef;
import org.eclipse.m2e.core.embedder.ArtifactRepositoryRef;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.lifecyclemapping.model.IPluginExecutionMetadata;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.MavenProjectUtils;
import org.eclipse.m2e.core.project.ResolverConfiguration;
import org.eclipse.m2e.core.project.configurator.MojoExecutionKey;
public class MavenProjectFacade implements IMavenProjectFacade, Serializable {
private static final long serialVersionUID = -3648172776786224087L;
private static final String CTX_EXECUTION_PLANS = MavenProjectFacade.class.getName() + "/executionPlans";
private static final String CTX_SETUP_EXECUTIONS = MavenProjectFacade.class.getName() + "/setupExecutions";
public static final String PROP_LIFECYCLE_MAPPING = MavenProjectFacade.class.getName() + "/lifecycleMapping";
public static final String PROP_CONFIGURATORS = MavenProjectFacade.class.getName() + "/configurators";
private final ProjectRegistryManager manager;
private final IFile pom;
private final File pomFile;
private final ResolverConfiguration resolverConfiguration;
private final long[] timestamp;
// cached values from mavenProject
private final ArtifactKey artifactKey;
private final List<String> modules;
private final String packaging;
private final IPath[] resourceLocations;
private final IPath[] testResourceLocations;
private final IPath[] compileSourceLocations;
private final IPath[] testCompileSourceLocations;
private final IPath outputLocation;
private final IPath testOutputLocation;
private final Set<ArtifactRepositoryRef> artifactRepositories;
private final Set<ArtifactRepositoryRef> pluginArtifactRepositories;
// TODO make final
private Set<ArtifactRef> artifacts; // dependencies are resolved after facade instance is created
// lifecycle mapping
// TODO make final
private String lifecycleMappingId;
// TODO make final
private Map<MojoExecutionKey, List<IPluginExecutionMetadata>> mojoExecutionMapping;
private transient Map<String, Object> sessionProperties;
public MavenProjectFacade(ProjectRegistryManager manager, IFile pom, MavenProject mavenProject,
ResolverConfiguration resolverConfiguration) {
this.manager = manager;
this.pom = pom;
IPath location = pom.getLocation();
this.pomFile = location == null ? null : location.toFile(); // save pom file
this.resolverConfiguration = resolverConfiguration;
this.artifactKey = new ArtifactKey(mavenProject.getArtifact());
this.packaging = mavenProject.getPackaging();
this.modules = mavenProject.getModules();
this.resourceLocations = MavenProjectUtils.getResourceLocations(getProject(), mavenProject.getResources());
this.testResourceLocations = MavenProjectUtils.getResourceLocations(getProject(), mavenProject.getTestResources());
this.compileSourceLocations = MavenProjectUtils.getSourceLocations(getProject(),
mavenProject.getCompileSourceRoots());
this.testCompileSourceLocations = MavenProjectUtils.getSourceLocations(getProject(),
mavenProject.getTestCompileSourceRoots());
IPath fullPath = getProject().getFullPath();
IPath path = getProjectRelativePath(mavenProject.getBuild().getOutputDirectory());
this.outputLocation = (path != null) ? fullPath.append(path) : null;
path = getProjectRelativePath(mavenProject.getBuild().getTestOutputDirectory());
this.testOutputLocation = path != null ? fullPath.append(path) : null;
this.artifactRepositories = new LinkedHashSet<ArtifactRepositoryRef>();
for(ArtifactRepository repository : mavenProject.getRemoteArtifactRepositories()) {
this.artifactRepositories.add(new ArtifactRepositoryRef(repository));
}
this.pluginArtifactRepositories = new LinkedHashSet<ArtifactRepositoryRef>();
for(ArtifactRepository repository : mavenProject.getPluginArtifactRepositories()) {
this.pluginArtifactRepositories.add(new ArtifactRepositoryRef(repository));
}
timestamp = new long[ProjectRegistryManager.METADATA_PATH.size() + 1];
IProject project = getProject();
int i = 0;
for(IPath metadataPath : ProjectRegistryManager.METADATA_PATH) {
timestamp[i] = getModificationStamp(project.getFile(metadataPath));
i++ ;
}
timestamp[timestamp.length - 1] = getModificationStamp(pom);
}
/**
* Copy constructor. Does NOT preserve session state.
*/
public MavenProjectFacade(MavenProjectFacade other) {
this.manager = other.manager;
this.pom = other.pom;
this.pomFile = other.pomFile;
this.resolverConfiguration = other.resolverConfiguration;
this.artifactKey = other.artifactKey;
this.packaging = other.packaging;
this.modules = new ArrayList<String>(other.modules);
this.resourceLocations = arrayCopy(other.resourceLocations);
this.testResourceLocations = arrayCopy(other.testResourceLocations);
this.compileSourceLocations = arrayCopy(other.compileSourceLocations);
this.testCompileSourceLocations = arrayCopy(other.testCompileSourceLocations);
this.outputLocation = other.outputLocation;
this.testOutputLocation = other.testOutputLocation;
this.artifactRepositories = new LinkedHashSet<ArtifactRepositoryRef>(other.artifactRepositories);
this.pluginArtifactRepositories = new LinkedHashSet<ArtifactRepositoryRef>(other.pluginArtifactRepositories);
this.timestamp = Arrays.copyOf(other.timestamp, other.timestamp.length);
}
private static <T> T[] arrayCopy(T[] a) {
return Arrays.copyOf(a, a.length);
}
/**
* Returns project relative paths of resource directories
*/
public IPath[] getResourceLocations() {
return resourceLocations;
}
/**
* Returns project relative paths of test resource directories
*/
public IPath[] getTestResourceLocations() {
return testResourceLocations;
}
public IPath[] getCompileSourceLocations() {
return compileSourceLocations;
}
public IPath[] getTestCompileSourceLocations() {
return testCompileSourceLocations;
}
/**
* Returns project resource for given file system location or null the location is outside of project.
*
* @param resourceLocation absolute file system location
* @return IPath the full, absolute workspace path resourceLocation
*/
public IPath getProjectRelativePath(String resourceLocation) {
return MavenProjectUtils.getProjectRelativePath(getProject(), resourceLocation);
}
/**
* Returns the full, absolute path of this project maven build output directory relative to the workspace or null if
* maven build output directory cannot be determined or outside of the workspace.
*/
public IPath getOutputLocation() {
return outputLocation;
}
/**
* Returns the full, absolute path of this project maven build test output directory relative to the workspace or null
* if maven build output directory cannot be determined or outside of the workspace.
*/
public IPath getTestOutputLocation() {
return testOutputLocation;
}
public IPath getFullPath() {
return getProject().getFullPath();
}
/**
* Lazy load and cache MavenProject instance
*/
public MavenProject getMavenProject(IProgressMonitor monitor) throws CoreException {
return manager.getMavenProject(this, monitor);
}
public MavenProject getMavenProject() {
return manager.getMavenProject(this);
}
public String getPackaging() {
return packaging;
}
public IProject getProject() {
return pom.getProject();
}
public IFile getPom() {
return pom;
}
public File getPomFile() {
return pomFile;
}
/**
* Returns the full, absolute path of the given file relative to the workspace. Returns null if the file does not
* exist or is not a member of this project.
*/
public IPath getFullPath(File file) {
return MavenProjectUtils.getFullPath(getProject(), file);
}
public List<String> getMavenProjectModules() {
return modules;
}
public Set<ArtifactRef> getMavenProjectArtifacts() {
return artifacts;
}
void setMavenProjectArtifacts(MavenProject mavenProject) {
this.artifacts = Collections.unmodifiableSet(ArtifactRef.fromArtifact(mavenProject.getArtifacts()));
}
public ResolverConfiguration getResolverConfiguration() {
return resolverConfiguration;
}
/**
* @return true if maven project needs to be re-read from disk
*/
public boolean isStale() {
IProject project = getProject();
int i = 0;
for(IPath path : ProjectRegistryManager.METADATA_PATH) {
if(timestamp[i] != getModificationStamp(project.getFile(path))) {
return true;
}
i++ ;
}
return false;
}
private static long getModificationStamp(IFile file) {
/*
* this implementation misses update in the following scenario
*
* 1. two files, A and B, with different content were created with same localTimeStamp
* 2. original A was deleted and B moved to A
*
* See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=160728
*/
return file.getLocalTimeStamp() + file.getModificationStamp();
}
public ArtifactKey getArtifactKey() {
return artifactKey;
}
public synchronized void setSessionProperty(String key, Object value) {
if(sessionProperties == null) {
sessionProperties = new HashMap<String, Object>();
}
if(value != null) {
sessionProperties.put(key, value);
} else {
sessionProperties.remove(key);
}
}
public synchronized Object getSessionProperty(String key) {
return sessionProperties != null ? sessionProperties.get(key) : null;
}
public Set<ArtifactRepositoryRef> getArtifactRepositoryRefs() {
return artifactRepositories;
}
public Set<ArtifactRepositoryRef> getPluginArtifactRepositoryRefs() {
return pluginArtifactRepositories;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getProject().toString()).append(": ").append(getArtifactKey().toString());
return sb.toString();
}
public String getLifecycleMappingId() {
return lifecycleMappingId;
}
public void setLifecycleMappingId(String lifecycleMappingId) {
this.lifecycleMappingId = lifecycleMappingId;
}
public Map<MojoExecutionKey, List<IPluginExecutionMetadata>> getMojoExecutionMapping() {
return mojoExecutionMapping;
}
public void setMojoExecutionMapping(Map<MojoExecutionKey, List<IPluginExecutionMetadata>> mojoExecutionMapping) {
this.mojoExecutionMapping = mojoExecutionMapping;
}
// mojo executions
/**
* Maps LIFECYCLE_* to corresponding mojo executions. The mojo executions are not fully setup and cannot be executed
* directly.
*/
private Map<String, List<MojoExecution>> getExecutionPlans(IProgressMonitor monitor) throws CoreException {
MavenProject mavenProject = getMavenProject(monitor);
Map<String, List<MojoExecution>> executionPlans = getContextValue(mavenProject, CTX_EXECUTION_PLANS);
if(executionPlans == null) {
executionPlans = manager.calculateExecutionPlans(pom, mavenProject, monitor);
mavenProject.setContextValue(CTX_EXECUTION_PLANS, executionPlans);
}
return executionPlans;
}
@SuppressWarnings("unchecked")
private static <T> T getContextValue(MavenProject mavenProject, String key) {
// XXX this is not thread safe, but needs to be fixed in maven, I can't fix this properly from m2e end
return (T) mavenProject.getContextValue(key);
}
/**
* Returns cache of fully setup MojoExecution instances. Lifespan of the cache is linked to the lifespan of
* MavenProject instance. The cache is initially empty and it is up to the callers to populate and maintain it.
*/
private Map<MojoExecutionKey, MojoExecution> getSetupMojoExecutions(IProgressMonitor monitor) throws CoreException {
MavenProject mavenProject = getMavenProject(monitor);
Map<MojoExecutionKey, MojoExecution> executionPlans = getContextValue(mavenProject, CTX_SETUP_EXECUTIONS);
if(executionPlans == null) {
executionPlans = new LinkedHashMap<MojoExecutionKey, MojoExecution>();
mavenProject.setContextValue(CTX_SETUP_EXECUTIONS, executionPlans);
}
return executionPlans;
}
public MojoExecution getMojoExecution(MojoExecutionKey mojoExecutionKey, IProgressMonitor monitor)
throws CoreException {
Map<MojoExecutionKey, MojoExecution> setupMojoExecutions = getSetupMojoExecutions(monitor);
MojoExecution execution = setupMojoExecutions.get(mojoExecutionKey);
if(execution == null) {
for(MojoExecution _execution : getMojoExecutions(monitor)) {
if(mojoExecutionKey.match(_execution)) {
execution = manager.setupMojoExecution(this, _execution, monitor);
break;
}
}
putSetupMojoExecution(setupMojoExecutions, mojoExecutionKey, execution);
}
return execution;
}
private void putSetupMojoExecution(Map<MojoExecutionKey, MojoExecution> setupMojoExecutions,
MojoExecutionKey mojoExecutionKey, MojoExecution execution) {
if(execution != null) {
setupMojoExecutions.put(mojoExecutionKey, execution);
}
}
public List<MojoExecution> getMojoExecutions(String groupId, String artifactId, IProgressMonitor monitor,
String... goals) throws CoreException {
List<MojoExecution> result = new ArrayList<MojoExecution>();
List<MojoExecution> _executions = getMojoExecutions(monitor);
if(_executions != null) {
for(MojoExecution _execution : _executions) {
if(groupId.equals(_execution.getGroupId()) && artifactId.equals(_execution.getArtifactId())
&& contains(goals, _execution.getGoal())) {
MojoExecutionKey _key = new MojoExecutionKey(_execution);
Map<MojoExecutionKey, MojoExecution> setupMojoExecutions = getSetupMojoExecutions(monitor);
MojoExecution execution = setupMojoExecutions.get(_key);
if(execution == null) {
execution = manager.setupMojoExecution(this, _execution, monitor);
putSetupMojoExecution(setupMojoExecutions, _key, execution);
}
result.add(execution);
}
}
}
return result;
}
private static boolean contains(String[] goals, String goal) {
for(int i = 0; i < goals.length; i++ ) {
if(goals[i].equals(goal)) {
return true;
}
}
return false;
}
/**
* Returns cached list of MojoExecutions bound to project's clean, default and site lifecycles. Returned
* MojoExecutions are not fully setup and {@link IMaven#setupMojoExecution(MavenSession, MavenProject, MojoExecution)}
* is required to execute and/or query mojo parameters. Similarly to {@link #getMavenProject()}, return value is null
* after workspace restart.
*/
public List<MojoExecution> getMojoExecutions() {
try {
return getMojoExecutions(null);
} catch(CoreException ex) {
return null;
}
}
/**
* Returns list of MojoExecutions bound to project's clean, default and site lifecycles. Returned MojoExecutions are
* not fully setup and {@link IMaven#setupMojoExecution(MavenSession, MavenProject, MojoExecution)} is required to
* execute and/or query mojo parameters.
*/
public List<MojoExecution> getMojoExecutions(IProgressMonitor monitor) throws CoreException {
Map<String, List<MojoExecution>> executionPlans = getExecutionPlans(monitor);
if(executionPlans == null) {
return null;
}
List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>();
for(List<MojoExecution> executionPlan : executionPlans.values()) {
if(executionPlan != null) { // null if execution plan could not be calculated
mojoExecutions.addAll(executionPlan);
}
}
return mojoExecutions;
}
public List<MojoExecution> getExecutionPlan(String lifecycle, IProgressMonitor monitor) throws CoreException {
Map<String, List<MojoExecution>> executionPlans = getExecutionPlans(monitor);
return executionPlans != null ? executionPlans.get(lifecycle) : null;
}
}