/******************************************************************************* * 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 v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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; } }