/******************************************************************************* * 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.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.osgi.util.NLS; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.lifecycle.MavenExecutionPlan; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.plugin.ExtensionRealmCache; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.PluginArtifactsCache; import org.apache.maven.plugin.PluginRealmCache; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.project.ProjectRealmCache; import org.apache.maven.project.artifact.MavenMetadataCache; import org.apache.maven.repository.DelegatingLocalArtifactRepository; import org.eclipse.m2e.core.embedder.ArtifactKey; import org.eclipse.m2e.core.embedder.ICallable; import org.eclipse.m2e.core.embedder.ILocalRepositoryListener; import org.eclipse.m2e.core.embedder.IMaven; import org.eclipse.m2e.core.embedder.IMavenExecutionContext; import org.eclipse.m2e.core.internal.ExtensionReader; import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.internal.Messages; import org.eclipse.m2e.core.internal.URLConnectionCaches; import org.eclipse.m2e.core.internal.builder.MavenBuilder; import org.eclipse.m2e.core.internal.embedder.MavenExecutionContext; import org.eclipse.m2e.core.internal.embedder.MavenImpl; import org.eclipse.m2e.core.internal.lifecyclemapping.LifecycleMappingFactory; import org.eclipse.m2e.core.internal.lifecyclemapping.LifecycleMappingResult; import org.eclipse.m2e.core.internal.lifecyclemapping.model.PluginExecutionMetadata; import org.eclipse.m2e.core.internal.markers.IMavenMarkerManager; import org.eclipse.m2e.core.internal.markers.MarkerUtils; import org.eclipse.m2e.core.internal.project.DependencyResolutionContext; import org.eclipse.m2e.core.internal.project.IManagedCache; import org.eclipse.m2e.core.internal.project.ResolverConfigurationIO; import org.eclipse.m2e.core.lifecyclemapping.model.IPluginExecutionMetadata; import org.eclipse.m2e.core.project.IMavenProjectChangedListener; import org.eclipse.m2e.core.project.IMavenProjectFacade; import org.eclipse.m2e.core.project.MavenProjectChangedEvent; import org.eclipse.m2e.core.project.MavenUpdateRequest; import org.eclipse.m2e.core.project.ResolverConfiguration; import org.eclipse.m2e.core.project.configurator.ILifecycleMapping; import org.eclipse.m2e.core.project.configurator.MojoExecutionKey; /** * This class keeps track of all maven projects present in the workspace and provides mapping between Maven and the * workspace. */ public class ProjectRegistryManager { static final Logger log = LoggerFactory.getLogger(ProjectRegistryManager.class); static final String ARTIFACT_TYPE_POM = "pom"; //$NON-NLS-1$ static final String ARTIFACT_TYPE_JAR = "jar"; //$NON-NLS-1$ public static final String ARTIFACT_TYPE_JAVA_SOURCE = "java-source"; //$NON-NLS-1$ public static final String ARTIFACT_TYPE_JAVADOC = "javadoc"; //$NON-NLS-1$ public static final String LIFECYCLE_DEFAULT = "deploy"; public static final String LIFECYCLE_CLEAN = "clean"; public static final String LIFECYCLE_SITE = "site"; /** * Path of project metadata files, relative to the project. These files are used to determine if project dependencies * need to be updated. */ public static final List<? extends IPath> METADATA_PATH = Arrays.asList( // new Path("pom.xml"), // //$NON-NLS-1$ new Path(".settings/" + IMavenConstants.PLUGIN_ID + ".prefs")); // dirty trick! //$NON-NLS-1$ //$NON-NLS-2$ private static final String CTX_MAVENPROJECTS = ProjectRegistryManager.class.getName() + "/mavenProjects"; private final ProjectRegistry projectRegistry; /*package*/final MavenImpl maven; /*package*/final IMavenMarkerManager markerManager; private final ProjectRegistryReader stateReader; private final Set<IMavenProjectChangedListener> projectChangeListeners = new LinkedHashSet<IMavenProjectChangedListener>(); private volatile Thread syncRefreshThread; /** * Backwards compatibility with clients that request setup MojoExecution outside of {@link MavenBuilder} execution. */ private final Map<MavenProjectFacade, MavenProject> legacyMavenProjects = new IdentityHashMap<MavenProjectFacade, MavenProject>(); private final Cache<MavenProjectFacade, MavenProject> mavenProjectCache; public ProjectRegistryManager(MavenImpl maven, File stateLocationDir, boolean readState, IMavenMarkerManager mavenMarkerManager) { this.markerManager = mavenMarkerManager; this.maven = maven; this.stateReader = new ProjectRegistryReader(stateLocationDir); ProjectRegistry state = readState && stateReader != null ? stateReader.readWorkspaceState(this) : null; this.projectRegistry = (state != null && state.isValid()) ? state : new ProjectRegistry(); this.mavenProjectCache = createProjectCache(); } /** * Creates or returns cached MavenProjectFacade for the given project. This method will not block if called from * IMavenProjectChangedListener#mavenProjectChanged */ public MavenProjectFacade create(IProject project, IProgressMonitor monitor) { return create(getPom(project), false, monitor); } /** * Returns MavenProjectFacade corresponding to the pom. This method first looks in the project cache, then attempts to * load the pom if the pom is not found in the cache. In the latter case, workspace resolution is assumed to be * enabled for the pom but the pom will not be added to the cache. */ public MavenProjectFacade create(IFile pom, boolean load, IProgressMonitor monitor) { if(pom == null) { return null; } // MavenProjectFacade projectFacade = (MavenProjectFacade) workspacePoms.get(pom.getFullPath()); MavenProjectFacade projectFacade = projectRegistry.getProjectFacade(pom); if(projectFacade == null && load) { ResolverConfiguration configuration = ResolverConfigurationIO.readResolverConfiguration(pom.getProject()); MavenExecutionResult executionResult = readProjectWithDependencies(projectRegistry, pom, configuration, monitor); MavenProject mavenProject = executionResult.getProject(); if(mavenProject != null) { projectFacade = new MavenProjectFacade(this, pom, mavenProject, configuration); } else { List<Throwable> exceptions = executionResult.getExceptions(); if(exceptions != null) { for(Throwable ex : exceptions) { String msg = "Failed to read Maven project: " + ex.getMessage(); log.error(msg, ex); } } } } return projectFacade; } IFile getPom(IProject project) { if(project == null || !project.isAccessible()) { // XXX sensible handling return null; } return project.getFile(IMavenConstants.POM_FILE_NAME); } /** * Removes specified poms from the cache. Adds dependent poms to pomSet but does not directly refresh dependent poms. * Recursively removes all nested modules if appropriate. * * @return a {@link Set} of {@link IFile} affected poms */ public Set<IFile> remove(MutableProjectRegistry state, Set<IFile> poms, boolean force) { Set<IFile> pomSet = new LinkedHashSet<IFile>(); for(Iterator<IFile> it = poms.iterator(); it.hasNext();) { IFile pom = it.next(); MavenProjectFacade facade = state.getProjectFacade(pom); if(force || facade == null || facade.isStale()) { pomSet.addAll(remove(state, pom)); } } return pomSet; } /** * Removes the pom from the cache. Adds dependent poms to pomSet but does not directly refresh dependent poms. * Recursively removes all nested modules if appropriate. * * @return a {@link Set} of {@link IFile} affected poms */ public Set<IFile> remove(MutableProjectRegistry state, IFile pom) { MavenProjectFacade facade = state.getProjectFacade(pom); ArtifactKey mavenProject = facade != null ? facade.getArtifactKey() : null; flushCaches(state, pom, facade, false); if(mavenProject == null) { state.removeProject(pom, null); return Collections.emptySet(); } Set<IFile> pomSet = new LinkedHashSet<IFile>(); pomSet.addAll(state.getDependents(MavenCapability.createMavenArtifact(mavenProject), false)); pomSet.addAll(state.getDependents(MavenCapability.createMavenParent(mavenProject), false)); // TODO check packaging state.removeProject(pom, mavenProject); pomSet.addAll(refreshWorkspaceModules(state, pom, mavenProject)); pomSet.remove(pom); return pomSet; } /** * @deprecated this method does not properly join {@link IMavenExecutionContext}, use * {@link #refresh(Set, IProgressMonitor)} instead. */ public void refresh(final MavenUpdateRequest request, final IProgressMonitor monitor) throws CoreException { getMaven().execute(request.isOffline(), request.isForceDependencyUpdate(), new ICallable<Void>() { public Void call(IMavenExecutionContext context, IProgressMonitor monitor) throws CoreException { refresh(request.getPomFiles(), monitor); return null; } }, monitor); } private boolean isForceDependencyUpdate() throws CoreException { MavenExecutionContext context = maven.getExecutionContext(); return context != null && context.getExecutionRequest().isUpdateSnapshots(); } /** * This method acquires workspace root's lock and sends project change events. It is meant for synchronous registry * updates. * * @since 1.4 */ public void refresh(final Collection<IFile> pomFiles, final IProgressMonitor monitor) throws CoreException { SubMonitor progress = SubMonitor.convert(monitor, Messages.ProjectRegistryManager_task_refreshing, 100); ISchedulingRule rule = ResourcesPlugin.getWorkspace().getRoot(); Job.getJobManager().beginRule(rule, progress); try { syncRefreshThread = Thread.currentThread(); MutableProjectRegistry newState = newMutableProjectRegistry(); try { refresh(newState, pomFiles, progress.newChild(95)); applyMutableProjectRegistry(newState, progress.newChild(5)); } finally { newState.close(); } } finally { syncRefreshThread = null; Job.getJobManager().endRule(rule); } } void refresh(final MutableProjectRegistry newState, Collection<IFile> pomFiles, IProgressMonitor monitor) throws CoreException { log.debug("Refreshing: {}", pomFiles); //$NON-NLS-1$ // 442524 safety guard URLConnectionCaches.assertDisabled(); final DependencyResolutionContext context = new DependencyResolutionContext(pomFiles); // safety net -- do not force refresh of the same installed/resolved artifact more than once final Set<ArtifactKey> installedArtifacts = new HashSet<ArtifactKey>(); ILocalRepositoryListener listener = new ILocalRepositoryListener() { public void artifactInstalled(File repositoryBasedir, ArtifactKey baseArtifact, ArtifactKey artifact, File artifactFile) { if(artifactFile == null) { // resolution error return; } // TODO remove=false? Set<IFile> refresh = new LinkedHashSet<IFile>(); if(installedArtifacts.add(artifact)) { refresh.addAll(newState.getVersionedDependents(MavenCapability.createMavenParent(artifact), true)); refresh.addAll(newState.getVersionedDependents(MavenCapability.createMavenArtifact(artifact), true)); refresh.addAll(newState.getVersionedDependents(MavenCapability.createMavenArtifactImport(artifact), true)); } if(installedArtifacts.add(baseArtifact)) { refresh.addAll(newState.getVersionedDependents(MavenCapability.createMavenParent(baseArtifact), true)); refresh.addAll(newState.getVersionedDependents(MavenCapability.createMavenArtifact(baseArtifact), true)); refresh .addAll(newState.getVersionedDependents(MavenCapability.createMavenArtifactImport(baseArtifact), true)); } if(!refresh.isEmpty()) { log.debug("Automatic refresh. artifact={}/{}. projects={}", new Object[] {baseArtifact, artifact, refresh}); context.forcePomFiles(refresh); } } }; maven.addLocalRepositoryListener(listener); try { refresh(newState, context, monitor); } finally { maven.removeLocalRepositoryListener(listener); } log.debug("Refreshed: {}", pomFiles); //$NON-NLS-1$ } private void refresh(final MutableProjectRegistry newState, final DependencyResolutionContext context, IProgressMonitor monitor) throws CoreException { Set<IFile> secondPhaseBacklog = new LinkedHashSet<IFile>(); final Map<IFile, Set<Capability>> originalCapabilities = new HashMap<IFile, Set<Capability>>(); final Map<IFile, Set<RequiredCapability>> originalRequirements = new HashMap<IFile, Set<RequiredCapability>>(); // phase 1: build projects without dependencies and populate workspace with known projects while(!context.isEmpty()) { if(monitor.isCanceled()) { throw new OperationCanceledException(); } if(newState.isStale() || (syncRefreshThread != null && syncRefreshThread != Thread.currentThread())) { throw new StaleMutableProjectRegistryException(); } IFile pom = context.pop(); monitor.subTask(NLS.bind(Messages.ProjectRegistryManager_task_project, pom.getProject().getName())); MavenProjectFacade oldFacade = newState.getProjectFacade(pom); context.forcePomFiles(flushCaches(newState, pom, oldFacade, isForceDependencyUpdate())); if(oldFacade != null) { putMavenProject(oldFacade, null); // maintain maven project cache } MavenProjectFacade newFacade = null; if(pom.isAccessible() && pom.getProject().hasNature(IMavenConstants.NATURE_ID)) { if(oldFacade != null) { // refresh old child modules MavenCapability mavenParentCapability = MavenCapability.createMavenParent(oldFacade.getArtifactKey()); context.forcePomFiles(newState.getVersionedDependents(mavenParentCapability, true)); // refresh projects that import dependencyManagement from this one MavenCapability mavenArtifactImportCapability = MavenCapability .createMavenArtifactImport(oldFacade.getArtifactKey()); context.forcePomFiles(newState.getVersionedDependents(mavenArtifactImportCapability, true)); } newFacade = readMavenProjectFacade(pom, context, newState, monitor); } else { // refresh children of deleted/closed parent if(oldFacade != null) { MavenCapability mavenParentCapability = MavenCapability.createMavenParent(oldFacade.getArtifactKey()); context.forcePomFiles(newState.getDependents(mavenParentCapability, true)); MavenCapability mavenArtifactImportCapability = MavenCapability .createMavenArtifactImport(oldFacade.getArtifactKey()); context.forcePomFiles(newState.getVersionedDependents(mavenArtifactImportCapability, true)); } } newState.setProject(pom, newFacade); if(newFacade != null) { // refresh new child modules MavenCapability mavenParentCapability = MavenCapability.createMavenParent(newFacade.getArtifactKey()); context.forcePomFiles(newState.getVersionedDependents(mavenParentCapability, true)); // refresh projects that import dependencyManagement from this one MavenCapability mavenArtifactImportCapability = MavenCapability .createMavenArtifactImport(newFacade.getArtifactKey()); context.forcePomFiles(newState.getVersionedDependents(mavenArtifactImportCapability, true)); Set<Capability> capabilities = new LinkedHashSet<Capability>(); capabilities.add(mavenParentCapability); capabilities.add(MavenCapability.createMavenArtifact(newFacade.getArtifactKey())); Set<Capability> oldCapabilities = newState.setCapabilities(pom, capabilities); if(!originalCapabilities.containsKey(pom)) { originalCapabilities.put(pom, oldCapabilities); } MavenProject mavenProject = getMavenProject(newFacade); Set<RequiredCapability> requirements = new LinkedHashSet<RequiredCapability>(); DefaultMavenDependencyResolver.addProjectStructureRequirements(requirements, mavenProject); Set<RequiredCapability> oldRequirements = newState.setRequirements(pom, requirements); if(!originalRequirements.containsKey(pom)) { originalRequirements.put(pom, oldRequirements); } } // at this point project facade and project capabilities/requirements are inconsistent in the state // this will be reconciled during the second phase secondPhaseBacklog.add(pom); } context.forcePomFiles(secondPhaseBacklog); // phase 2: resolve project dependencies Set<IFile> secondPhaseProcessed = new HashSet<IFile>(); while(!context.isEmpty()) { if(monitor.isCanceled()) { throw new OperationCanceledException(); } if(newState.isStale() || (syncRefreshThread != null && syncRefreshThread != Thread.currentThread())) { throw new StaleMutableProjectRegistryException(); } final IFile pom = context.pop(); if(!secondPhaseProcessed.add(pom)) { // because workspace contents is fully known at this point, each project needs to be resolved at most once continue; } MavenProjectFacade newFacade = null; if(pom.isAccessible() && pom.getProject().hasNature(IMavenConstants.NATURE_ID)) { newFacade = newState.getProjectFacade(pom); } if(newFacade != null) { MavenProject mavenProject = getMavenProject(newFacade); if(mavenProject == null) { // facade from workspace state that has not been refreshed yet newFacade = readMavenProjectFacade(pom, context, newState, monitor); } else { // recreate facade instance to trigger project changed event // this is only necessary for facades that are refreshed because their dependencies changed // but this is relatively cheap, so all facades are recreated here putMavenProject(newFacade, null); newFacade = new MavenProjectFacade(newFacade); putMavenProject(newFacade, mavenProject); } } if(newFacade != null) { final MavenProjectFacade _newFacade = newFacade; final MavenProject mavenProject = getMavenProject(newFacade); final ResolverConfiguration resolverConfiguration = _newFacade.getResolverConfiguration(); final ICallable<Void> callable = new ICallable<Void>() { public Void call(IMavenExecutionContext executionContext, IProgressMonitor monitor) throws CoreException { refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, _newFacade, monitor); return null; } }; createExecutionContext(newState, pom, resolverConfiguration).execute(mavenProject, callable, monitor); } else { refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, newFacade, monitor); } monitor.worked(1); } } void refreshPhase2(MutableProjectRegistry newState, DependencyResolutionContext context, Map<IFile, Set<Capability>> originalCapabilities, Map<IFile, Set<RequiredCapability>> originalRequirements, IFile pom, MavenProjectFacade newFacade, IProgressMonitor monitor) throws CoreException { Set<Capability> capabilities = null; Set<RequiredCapability> requirements = null; if(newFacade != null) { monitor.subTask(NLS.bind(Messages.ProjectRegistryManager_task_project, newFacade.getProject().getName())); setupLifecycleMapping(newState, monitor, newFacade); capabilities = new LinkedHashSet<Capability>(); requirements = new LinkedHashSet<RequiredCapability>(); Capability mavenParentCapability = MavenCapability.createMavenParent(newFacade.getArtifactKey()); // maven projects always have these capabilities capabilities.add(MavenCapability.createMavenArtifact(newFacade.getArtifactKey())); capabilities.add(mavenParentCapability); // TODO consider packaging // maven projects always have these requirements DefaultMavenDependencyResolver.addProjectStructureRequirements(requirements, getMavenProject(newFacade)); AbstractMavenDependencyResolver resolver = getMavenDependencyResolver(newFacade, monitor); resolver.setContextProjectRegistry(newState); try { resolver.resolveProjectDependencies(newFacade, capabilities, requirements, monitor); } finally { resolver.setContextProjectRegistry(null); } newState.setProject(pom, newFacade); newFacade.setMavenProjectArtifacts(getMavenProject(newFacade)); } else { if(pom.isAccessible() && pom.getProject().hasNature(IMavenConstants.NATURE_ID)) { try { // MNGECLIPSE-605 embedder is not able to resolve the project due to missing configuration in the parent Model model = getMaven().readModel(pom.getLocation().toFile()); if(model != null && model.getParent() != null) { Parent parent = model.getParent(); if(parent.getGroupId() != null && parent.getArtifactId() != null && parent.getVersion() != null) { ArtifactKey parentKey = new ArtifactKey(parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), null); requirements = new HashSet<RequiredCapability>(); requirements.add(MavenRequiredCapability.createMavenParent(parentKey)); } } } catch(Exception e) { // we've tried our best, there is nothing else we can do log.error(e.getMessage(), e); } } } Set<Capability> oldCapabilities = newState.setCapabilities(pom, capabilities); if(originalCapabilities.containsKey(pom)) { oldCapabilities = originalCapabilities.get(pom); } // if our capabilities changed, recalculate everyone who depends on new/changed/removed capabilities Set<Capability> changedCapabilities = diff(oldCapabilities, capabilities); // refresh versioned dependents if only parent capability has changed boolean versionedCapabilitiesOnly = true; for(Capability capability : changedCapabilities) { if(MavenCapability.NS_MAVEN_ARTIFACT.equals(capability.getVersionlessKey().getNamespace())) { versionedCapabilitiesOnly = false; break; } } for(Capability capability : changedCapabilities) { context.forcePomFiles(versionedCapabilitiesOnly ? newState.getVersionedDependents(capability, true) : newState.getDependents(capability, true)); } Set<RequiredCapability> oldRequirements = newState.setRequirements(pom, requirements); if(originalRequirements.containsKey(pom)) { oldRequirements = originalRequirements.get(pom); } // if our dependencies changed, recalculate everyone who depends on us // this is needed to deal with transitive dependency resolution in maven if(oldCapabilities != null && hasDiff(oldRequirements, requirements)) { for(Capability capability : oldCapabilities) { context.forcePomFiles(newState.getVersionedDependents(capability, true)); } } } private void setupLifecycleMapping(MutableProjectRegistry newState, IProgressMonitor monitor, MavenProjectFacade newFacade) throws CoreException { LifecycleMappingResult mappingResult = LifecycleMappingFactory.calculateLifecycleMapping(getMavenProject(newFacade), newFacade.getMojoExecutions(), newFacade.getResolverConfiguration().getLifecycleMappingId(), monitor); newFacade.setLifecycleMappingId(mappingResult.getLifecycleMappingId()); Map<MojoExecutionKey, List<IPluginExecutionMetadata>> mojoExecutionMapping = mappingResult .getMojoExecutionMapping(); if(mojoExecutionMapping != null) { detachMappingSources(mojoExecutionMapping); } newFacade.setMojoExecutionMapping(mojoExecutionMapping); // XXX reconcile with corresponding LifecycleMappingFactory methods newFacade.setSessionProperty(MavenProjectFacade.PROP_LIFECYCLE_MAPPING, mappingResult.getLifecycleMapping()); LifecycleMappingFactory.setProjectConfigurators(newFacade, mappingResult); markerManager.deleteMarkers(newFacade.getPom(), IMavenConstants.MARKER_LIFECYCLEMAPPING_ID); if(mappingResult.hasProblems()) { markerManager.addErrorMarkers(newFacade.getPom(), IMavenConstants.MARKER_LIFECYCLEMAPPING_ID, mappingResult.getProblems()); } } private void detachMappingSources(Map<MojoExecutionKey, List<IPluginExecutionMetadata>> mapping) { for(List<IPluginExecutionMetadata> executions : mapping.values()) { if(executions != null) { ListIterator<IPluginExecutionMetadata> iterator = executions.listIterator(); while(iterator.hasNext()) { PluginExecutionMetadata execution = (PluginExecutionMetadata) iterator.next(); execution = execution.clone(); execution.setSource(null); iterator.set(execution); } } } } static <T> Set<T> diff(Set<T> a, Set<T> b) { if(a == null || a.isEmpty()) { if(b == null || b.isEmpty()) { return Collections.emptySet(); } return b; } if(b == null || b.isEmpty()) { return a; } Set<T> result = new HashSet<T>(); Set<T> t; t = new HashSet<T>(a); t.removeAll(b); result.addAll(t); t = new HashSet<T>(b); t.removeAll(a); result.addAll(t); return result; } static <T> boolean hasDiff(Set<T> a, Set<T> b) { if(a == null || a.isEmpty()) { return b != null && !b.isEmpty(); } if(b == null || b.isEmpty()) { return true; } if(a.size() != b.size()) { return true; } Iterator<T> oldIter = a.iterator(); Iterator<T> iter = b.iterator(); while(oldIter.hasNext()) { T oldRequirement = oldIter.next(); T requirement = iter.next(); if(!oldRequirement.equals(requirement)) { return true; } } return false; } private AbstractMavenDependencyResolver getMavenDependencyResolver(MavenProjectFacade newFacade, IProgressMonitor monitor) { ILifecycleMapping lifecycleMapping = LifecycleMappingFactory.getLifecycleMapping(newFacade); if(lifecycleMapping instanceof ILifecycleMapping2) { AbstractMavenDependencyResolver resolver = ((ILifecycleMapping2) lifecycleMapping).getDependencyResolver(monitor); resolver.setManager(this); return resolver; } return new DefaultMavenDependencyResolver(this, markerManager); } private MavenProjectFacade readMavenProjectFacade(final IFile pom, DependencyResolutionContext context, final MutableProjectRegistry state, final IProgressMonitor monitor) throws CoreException { markerManager.deleteMarkers(pom, IMavenConstants.MARKER_POM_LOADING_ID); final ResolverConfiguration resolverConfiguration = ResolverConfigurationIO .readResolverConfiguration(pom.getProject()); return execute(state, pom, resolverConfiguration, new ICallable<MavenProjectFacade>() { public MavenProjectFacade call(IMavenExecutionContext context, IProgressMonitor monitor) throws CoreException { MavenProject mavenProject = null; MavenExecutionResult mavenResult = null; if(pom.isAccessible()) { mavenResult = getMaven().readMavenProject(pom.getLocation().toFile(), context.newProjectBuildingRequest()); mavenProject = mavenResult.getProject(); } MarkerUtils.addEditorHintMarkers(markerManager, pom, mavenProject, IMavenConstants.MARKER_POM_LOADING_ID); markerManager.addMarkers(pom, IMavenConstants.MARKER_POM_LOADING_ID, mavenResult); if(mavenProject == null) { return null; } // create and return new project facade MavenProjectFacade mavenProjectFacade = new MavenProjectFacade(ProjectRegistryManager.this, pom, mavenProject, resolverConfiguration); putMavenProject(mavenProjectFacade, mavenProject); // maintain maven project cache return mavenProjectFacade; } }, monitor); } /*package*/Map<String, List<MojoExecution>> calculateExecutionPlans(IFile pom, MavenProject mavenProject, IProgressMonitor monitor) { Map<String, List<MojoExecution>> executionPlans = new LinkedHashMap<String, List<MojoExecution>>(); executionPlans.put(LIFECYCLE_CLEAN, calculateExecutionPlan(pom, mavenProject, LIFECYCLE_CLEAN, monitor)); executionPlans.put(LIFECYCLE_DEFAULT, calculateExecutionPlan(pom, mavenProject, LIFECYCLE_DEFAULT, monitor)); executionPlans.put(LIFECYCLE_SITE, calculateExecutionPlan(pom, mavenProject, LIFECYCLE_SITE, monitor)); return executionPlans; } private List<MojoExecution> calculateExecutionPlan(IFile pom, final MavenProject mavenProject, final String lifecycle, final IProgressMonitor monitor) { List<MojoExecution> mojoExecutions = null; try { MavenExecutionPlan executionPlan = maven.calculateExecutionPlan(mavenProject, Arrays.asList(lifecycle), false, monitor); return executionPlan.getMojoExecutions(); } catch(CoreException e) { markerManager.addErrorMarkers(pom, IMavenConstants.MARKER_POM_LOADING_ID, e); } return mojoExecutions; } public IFile getModulePom(IFile pom, String moduleName) { return pom.getParent().getFile(new Path(moduleName).append(IMavenConstants.POM_FILE_NAME)); } private Set<IFile> refreshWorkspaceModules(MutableProjectRegistry state, IFile pom, ArtifactKey mavenProject) { if(mavenProject == null) { return Collections.emptySet(); } return state.removeWorkspaceModules(pom, mavenProject); } public void addMavenProjectChangedListener(IMavenProjectChangedListener listener) { synchronized(projectChangeListeners) { projectChangeListeners.add(listener); } } public void removeMavenProjectChangedListener(IMavenProjectChangedListener listener) { if(listener == null) { return; } synchronized(projectChangeListeners) { projectChangeListeners.remove(listener); } } public void notifyProjectChangeListeners(List<MavenProjectChangedEvent> events, IProgressMonitor monitor) { if(events.size() > 0) { MavenProjectChangedEvent[] eventsArray = events.toArray(new MavenProjectChangedEvent[events.size()]); ArrayList<IMavenProjectChangedListener> listeners = new ArrayList<IMavenProjectChangedListener>(); synchronized(this.projectChangeListeners) { listeners.addAll(this.projectChangeListeners); } listeners.addAll(ExtensionReader.readProjectChangedEventListenerExtentions()); for(IMavenProjectChangedListener listener : listeners) { listener.mavenProjectChanged(eventsArray, monitor); } } } public MavenProjectFacade getMavenProject(String groupId, String artifactId, String version) { return projectRegistry.getProjectFacade(groupId, artifactId, version); } MavenProject readProjectWithDependencies(IFile pomFile, ResolverConfiguration resolverConfiguration, IProgressMonitor monitor) throws CoreException { MavenExecutionResult result = readProjectWithDependencies(projectRegistry, pomFile, resolverConfiguration, monitor); MavenProject mavenProject = result.getProject(); if(mavenProject != null) { return mavenProject; } MultiStatus status = new MultiStatus(IMavenConstants.PLUGIN_ID, 0, Messages.MavenProjectFacade_error, null); List<Throwable> exceptions = result.getExceptions(); for(Throwable e : exceptions) { status.add(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, 0, e.getMessage(), e)); } throw new CoreException(status); } private MavenExecutionResult readProjectWithDependencies(IProjectRegistry state, final IFile pomFile, ResolverConfiguration resolverConfiguration, final IProgressMonitor monitor) { try { return execute(state, pomFile, resolverConfiguration, new ICallable<MavenExecutionResult>() { public MavenExecutionResult call(IMavenExecutionContext context, IProgressMonitor monitor) throws CoreException { ProjectBuildingRequest configuration = context.newProjectBuildingRequest(); configuration.setResolveDependencies(true); return getMaven().readMavenProject(pomFile.getLocation().toFile(), configuration); } }, monitor); } catch(CoreException ex) { DefaultMavenExecutionResult result = new DefaultMavenExecutionResult(); result.addException(ex); return result; } } public IMavenProjectFacade[] getProjects() { return projectRegistry.getProjects(); } public IMavenProjectFacade getProject(IProject project) { return projectRegistry.getProjectFacade(getPom(project)); } /** * Context */ static class Context { final IProjectRegistry state; final ResolverConfiguration resolverConfiguration; final IFile pom; Context(IProjectRegistry state, ResolverConfiguration resolverConfiguration, IFile pom) { this.state = state; this.resolverConfiguration = resolverConfiguration; this.pom = pom; } } /** * @deprecated This method does not properly join {@link IMavenExecutionContext} */ public MavenExecutionRequest createExecutionRequest(IFile pom, ResolverConfiguration resolverConfiguration, IProgressMonitor monitor) throws CoreException { MavenExecutionRequest request = getMaven().createExecutionRequest(monitor); configureExecutionRequest(request, projectRegistry, pom, resolverConfiguration); MavenExecutionContext.populateSystemProperties(request); return request; } /*package*/MavenExecutionRequest configureExecutionRequest(MavenExecutionRequest request, IProjectRegistry state, IFile pom, ResolverConfiguration resolverConfiguration) throws CoreException { request.setPom(pom.getLocation().toFile()); request.addActiveProfiles(resolverConfiguration.getActiveProfileList()); request.addInactiveProfiles(resolverConfiguration.getInactiveProfileList()); Properties p = request.getUserProperties(); Properties addProperties = resolverConfiguration.getProperties(); if(addProperties != null) { if(p == null) { p = new Properties(); } p.putAll(addProperties); } // eclipse workspace repository implements both workspace dependency resolution // and inter-module dependency resolution for multi-module projects. request.setLocalRepository(getMaven().getLocalRepository()); request.setWorkspaceReader(getWorkspaceReader(state, pom, resolverConfiguration)); return request; } private EclipseWorkspaceArtifactRepository getWorkspaceReader(IProjectRegistry state, IFile pom, ResolverConfiguration resolverConfiguration) { Context context = new Context(state, resolverConfiguration, pom); EclipseWorkspaceArtifactRepository workspaceReader = new EclipseWorkspaceArtifactRepository(context); return workspaceReader; } public MavenArtifactRepository getWorkspaceLocalRepository() throws CoreException { ResolverConfiguration resolverConfiguration = new ResolverConfiguration(); resolverConfiguration.setResolveWorkspaceProjects(true); EclipseWorkspaceArtifactRepository workspaceReader = getWorkspaceReader(projectRegistry, null, resolverConfiguration); DelegatingLocalArtifactRepository localRepo = new DelegatingLocalArtifactRepository( getMaven().getLocalRepository()); localRepo.setIdeWorkspace(workspaceReader); return localRepo; } MutableProjectRegistry newMutableProjectRegistry() { return new MutableProjectRegistry(projectRegistry); } /** * Applies mutable project registry to the primary project registry and and corresponding MavenProjectChangedEvent's * to all registered IMavenProjectChangedListener's. This method must be called from a thread holding workspace root's * lock. * * @throws StaleMutableProjectRegistryException if primary project registry was modified after mutable registry has * been created */ void applyMutableProjectRegistry(MutableProjectRegistry newState, IProgressMonitor monitor) throws CoreException { // don't cache maven sessions for(MavenProjectFacade facade : newState.getProjects()) { MavenProject mavenProject = getMavenProject(facade); if(mavenProject != null) { getMaven().detachFromSession(mavenProject); } } List<MavenProjectChangedEvent> events = projectRegistry.apply(newState); //stateReader.writeWorkspaceState(projectRegistry); notifyProjectChangeListeners(events, monitor); } public void writeWorkspaceState() { if(stateReader != null && projectRegistry != null) { stateReader.writeWorkspaceState(projectRegistry); } } IMaven getMaven() { return maven; } /*package*/MojoExecution setupMojoExecution(MavenProjectFacade projectFacade, MojoExecution mojoExecution, IProgressMonitor monitor) throws CoreException { MavenProject mavenProject = null; if(MavenExecutionContext.getThreadContext() == null) { // the intent of this code is to provide backwards compatibility for clients that request setup MojoExecution // outside of MavenBuilder context. Setup MojoExecutions are specific to MavenProject instances, so keep // MavenProject until the facade is discarded synchronized(legacyMavenProjects) { mavenProject = legacyMavenProjects.get(projectFacade); if(mavenProject == null) { mavenProject = getMavenProject(projectFacade, monitor); legacyMavenProjects.put(projectFacade, mavenProject); } } } if(mavenProject == null) { mavenProject = getMavenProject(projectFacade, monitor); } return maven.setupMojoExecution(mavenProject, mojoExecution, monitor); } private <V> V execute(final IProjectRegistry state, final IFile pom, final ResolverConfiguration resolverConfiguration, final ICallable<V> callable, final IProgressMonitor monitor) throws CoreException { return createExecutionContext(state, pom, resolverConfiguration).execute(callable, monitor); } private MavenExecutionContext createExecutionContext(final IProjectRegistry state, final IFile pom, final ResolverConfiguration resolverConfiguration) throws CoreException { MavenExecutionContext context = maven.createExecutionContext(); configureExecutionRequest(context.getExecutionRequest(), state, pom, resolverConfiguration); return context; } public MavenExecutionContext createExecutionContext(final IFile pom, final ResolverConfiguration resolverConfiguration) throws CoreException { return createExecutionContext(projectRegistry, pom, resolverConfiguration); } /** * There are three MavenProjectFacade-to-MavenProject maps. * <ul> * <li>Each MavenExecutionContext has "context project map" that guarantees consistent facade-project association for * entire lifespan of the context. In other words, calling facade.getMavenProject multiple times from within the same * maven execution scope is guaranteed to return the same MavenProject instance.</li> * <li>Global "project cache", that is meant to improve performance during incremental workspace builds. The project * cache is small and cached values are discarded and reloaded as needed.</li> * <li>Global "legacy support project map" provides support for legacy, i.e. pre m2e 1.4, extensions that setup * MojoExecution instances outside of maven execution scope. Legacy support project map entries are not discarded * until their corresponding facade instances are discarded.</li> * </ul> */ MavenProject getMavenProject(final MavenProjectFacade facade, final IProgressMonitor monitor) throws CoreException { MavenProject mavenProject; synchronized(legacyMavenProjects) { mavenProject = legacyMavenProjects.get(facade); } if(mavenProject != null) { return mavenProject; } Map<MavenProjectFacade, MavenProject> mavenProjects = getContextProjects(); mavenProject = mavenProjects.get(facade); if(mavenProject == null) { try { mavenProject = mavenProjectCache.get(facade, new Callable<MavenProject>() { public MavenProject call() throws Exception { return readProjectWithDependencies(facade.getPom(), facade.getResolverConfiguration(), monitor); } }); } catch(ExecutionException ex) { Throwable cause = ex.getCause(); if(cause instanceof CoreException) { throw (CoreException) cause; } throw new RuntimeException(cause); // this really should never happen } mavenProjects.put(facade, mavenProject); } return mavenProject; } MavenProject getMavenProject(MavenProjectFacade facade) { MavenProject mavenProject; synchronized(legacyMavenProjects) { mavenProject = legacyMavenProjects.get(facade); } if(mavenProject == null) { mavenProject = mavenProjectCache.getIfPresent(facade); if(mavenProject != null) { putMavenProject(facade, mavenProject); } } if(mavenProject == null) { mavenProject = getContextProjects().get(facade); } return mavenProject; } /** * @noreference public for test purposes only */ public void putMavenProject(MavenProjectFacade facade, MavenProject mavenProject) { Map<MavenProjectFacade, MavenProject> mavenProjects = getContextProjects(); if(mavenProject != null) { mavenProjects.put(facade, mavenProject); } else { mavenProjects.remove(facade); synchronized(legacyMavenProjects) { legacyMavenProjects.remove(facade); } } } Map<MavenProjectFacade, MavenProject> getContextProjects() { Map<MavenProjectFacade, MavenProject> projects = null; MavenExecutionContext context = MavenExecutionContext.getThreadContext(false); if(context != null) { projects = context.getValue(CTX_MAVENPROJECTS); if(projects == null) { projects = new IdentityHashMap<MavenProjectFacade, MavenProject>(); context.setValue(CTX_MAVENPROJECTS, projects); } } if(projects == null) { projects = new IdentityHashMap<MavenProjectFacade, MavenProject>(); } return projects; } private Cache<MavenProjectFacade, MavenProject> createProjectCache() { final RemovalListener<MavenProjectFacade, MavenProject> removalListener = new RemovalListener<MavenProjectFacade, MavenProject>() { public void onRemoval(RemovalNotification<MavenProjectFacade, MavenProject> notification) { if(notification.getCause() == RemovalCause.SIZE || notification.getCause() == RemovalCause.REPLACED) { // there is currently no good way to determine if MavenProject instance is still being used or not // for now assume that cache entries removed from project cache can only be referenced by context map final MavenProjectFacade facade = notification.getKey(); final MavenProject mavenProject = notification.getValue(); final Map<MavenProjectFacade, MavenProject> contextProjects = getContextProjects(); if(contextProjects != null && !contextProjects.containsKey(facade)) { flushMavenCaches(facade.getPomFile(), facade.getArtifactKey(), mavenProject, false); } } } }; return CacheBuilder.newBuilder().maximumSize(5).removalListener(removalListener).build(); } private Set<IFile> flushCaches(MutableProjectRegistry newState, IFile pom, MavenProjectFacade facade, boolean forceDependencyUpdate) { ArtifactKey key = null; MavenProject project = null; if(facade != null) { key = facade.getArtifactKey(); project = getMavenProject(facade); // cached only mavenProjectCache.invalidate(facade); Set<IFile> ifiles = new HashSet<>(); for(File file : flushMavenCaches(facade.getPomFile(), key, project, forceDependencyUpdate)) { MavenProjectFacade affected = projectRegistry.getProjectFacade(file); if(affected != null) { ifiles.add(affected.getPom()); } } return ifiles; } return Collections.emptySet(); } /** * Flushes caches maintained by Maven core. */ Set<File> flushMavenCaches(File pom, ArtifactKey key, MavenProject project, boolean force) { Set<File> affected = new HashSet<>(); affected.addAll(flushMavenCache(ProjectRealmCache.class, pom, key, force)); affected.addAll(flushMavenCache(ExtensionRealmCache.class, pom, key, force)); affected.addAll(flushMavenCache(PluginRealmCache.class, pom, key, force)); affected.addAll(flushMavenCache(MavenMetadataCache.class, pom, key, force)); affected.addAll(flushMavenCache(PluginArtifactsCache.class, pom, key, force)); return affected; } private Set<File> flushMavenCache(Class<?> clazz, File pom, ArtifactKey key, boolean force) { try { IManagedCache cache = (IManagedCache) maven.getPlexusContainer().lookup(clazz); return cache.removeProject(pom, key, force); } catch(ComponentLookupException ex) { // can't really happen } catch(CoreException ex) { // can't really happen } return Collections.emptySet(); } }