/*******************************************************************************
* Copyright (c) 2013 Igor Fedorenko
* 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:
* Igor Fedorenko - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.core.internal.embedder;
import static org.eclipse.m2e.core.internal.M2EUtils.copyProperties;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.eclipse.aether.transfer.TransferListener;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.properties.internal.EnvironmentUtils;
import org.apache.maven.session.scope.internal.SessionScope;
import org.eclipse.m2e.core.embedder.ICallable;
import org.eclipse.m2e.core.embedder.IMavenExecutionContext;
/**
* @since 1.4
*/
public class MavenExecutionContext implements IMavenExecutionContext {
private static final String CTX_PREFIX = MavenExecutionContext.class.getName();
private static final String CTX_LOCALREPOSITORY = CTX_PREFIX + "/localRepository";
private static final String CTX_MAVENSESSION = CTX_PREFIX + "/mavenSession";
private static final String CTX_REPOSITORYSESSION = CTX_PREFIX + "/repositorySession";
private static final ThreadLocal<Deque<MavenExecutionContext>> threadLocal = new ThreadLocal<Deque<MavenExecutionContext>>();
private final MavenImpl maven;
private MavenExecutionRequest request;
// TODO maybe delegate to parent context
private Map<String, Object> context;
public MavenExecutionContext(MavenImpl maven) {
this.maven = maven;
}
public MavenExecutionRequest getExecutionRequest() throws CoreException {
if(request != null && context != null) {
return new ReadonlyMavenExecutionRequest(request);
}
if(request == null) {
request = newExecutionRequest();
}
return request;
}
protected MavenExecutionRequest newExecutionRequest() throws CoreException {
MavenExecutionRequest request = null;
Deque<MavenExecutionContext> stack = threadLocal.get();
if(stack != null && !stack.isEmpty()) {
MavenExecutionRequest parent = stack.peek().request;
if(parent == null) {
throw new IllegalStateException(); //
}
request = DefaultMavenExecutionRequest.copy(parent);
}
if(request == null) {
request = maven.createExecutionRequest();
}
return request;
}
public <V> V execute(ICallable<V> callable, IProgressMonitor monitor) throws CoreException {
return execute(null, callable, monitor);
}
public <V> V execute(MavenProject project, ICallable<V> callable, IProgressMonitor monitor) throws CoreException {
Deque<MavenExecutionContext> stack = threadLocal.get();
if(stack == null) {
stack = new ArrayDeque<MavenExecutionContext>();
threadLocal.set(stack);
}
final MavenExecutionContext parent = stack.peek();
if(this == parent) {
// shortcut the setup logic, this is nested invocation of the same context
return executeBare(project, callable, monitor);
}
// remember original configuration to "pop" the session stack properly
final MavenExecutionRequest origRequest = request;
final Map<String, Object> origContext = context;
if(request == null && parent != null) {
this.request = parent.request;
this.context = new HashMap<String, Object>(parent.context);
} else {
this.context = new HashMap<String, Object>();
if(request == null) {
request = newExecutionRequest();
}
maven.populateDefaults(request);
populateSystemProperties(request);
setValue(CTX_LOCALREPOSITORY, request.getLocalRepository());
final FilterRepositorySystemSession repositorySession = maven.createRepositorySession(request);
setValue(CTX_REPOSITORYSESSION, repositorySession);
if(parent != null) {
repositorySession.setData(parent.getRepositorySession().getData());
}
final MavenExecutionResult result = new DefaultMavenExecutionResult();
setValue(CTX_MAVENSESSION, new MavenSession(maven.getPlexusContainer(), repositorySession, request, result));
}
final LegacySupport legacySupport = maven.lookup(LegacySupport.class);
final MavenSession origLegacySession = legacySupport.getSession(); // TODO validate == origSession
stack.push(this);
final MavenSession session = getSession();
legacySupport.setSession(session);
final SessionScope sessionScope = maven.lookup(SessionScope.class);
sessionScope.enter();
sessionScope.seed(MavenSession.class, session);
try {
return executeBare(project, callable, monitor);
} finally {
sessionScope.exit();
stack.pop();
if(stack.isEmpty()) {
threadLocal.set(null); // TODO decide if this is useful
}
legacySupport.setSession(origLegacySession);
request = origRequest;
context = origContext;
}
}
private <V> V executeBare(MavenProject project, ICallable<V> callable, IProgressMonitor monitor) throws CoreException {
final MavenSession mavenSession = getSession();
final FilterRepositorySystemSession repositorySession = getRepositorySession();
final TransferListener origTransferListener = repositorySession.setTransferListener(maven
.createArtifactTransferListener(monitor));
final MavenProject origProject = mavenSession.getCurrentProject();
final List<MavenProject> origProjects = mavenSession.getProjects();
final ClassLoader origTCCL = Thread.currentThread().getContextClassLoader();
try {
if(project != null) {
mavenSession.setCurrentProject(project);
mavenSession.setProjects(Collections.singletonList(project));
}
return callable.call(this, monitor);
} finally {
Thread.currentThread().setContextClassLoader(origTCCL);
repositorySession.setTransferListener(origTransferListener);
if(project != null) {
mavenSession.setCurrentProject(origProject);
mavenSession.setProjects(origProjects != null ? origProjects : Collections.<MavenProject> emptyList());
}
}
}
public MavenSession getSession() {
if(context == null) {
throw new IllegalStateException();
}
return getValue(CTX_MAVENSESSION);
}
public ArtifactRepository getLocalRepository() {
if(context == null) {
throw new IllegalStateException();
}
return getValue(CTX_LOCALREPOSITORY);
}
public FilterRepositorySystemSession getRepositorySession() {
if(context == null) {
throw new IllegalStateException();
}
return getValue(CTX_REPOSITORYSESSION);
}
public static MavenExecutionContext getThreadContext() {
return getThreadContext(true);
}
/**
* @since 1.5
*/
public static MavenExecutionContext getThreadContext(boolean innermost) {
final Deque<MavenExecutionContext> stack = threadLocal.get();
return stack != null ? (innermost ? stack.peekFirst() : stack.peekLast()) : null;
}
public static void populateSystemProperties(MavenExecutionRequest request) {
// temporary solution for https://issues.sonatype.org/browse/MNGECLIPSE-1607
// oddly, there are no unit tests that fail if this is commented out
Properties systemProperties = new Properties();
EnvironmentUtils.addEnvVars(systemProperties);
copyProperties(systemProperties, System.getProperties());
request.setSystemProperties(systemProperties);
}
/*
* <rant>Maven core does not provide good separation between session state, i.e. caches, settings, etc, and project
* building configuration, i.e. if dependencies should be resolve, resolution leniency, etc. On top of that, there is
* no easy way to create new populated ProjectBuildingRequest instances. Otherwise this method would not be
* needed.</rant>
*/
public ProjectBuildingRequest newProjectBuildingRequest() {
DefaultProjectBuildingRequest projectBuildingRequest = new DefaultProjectBuildingRequest();
projectBuildingRequest.setLocalRepository(getLocalRepository());
projectBuildingRequest.setRepositorySession(getRepositorySession());
projectBuildingRequest.setSystemProperties(request.getSystemProperties());
projectBuildingRequest.setUserProperties(request.getUserProperties());
projectBuildingRequest.setRemoteRepositories(request.getRemoteRepositories());
projectBuildingRequest.setPluginArtifactRepositories(request.getPluginArtifactRepositories());
projectBuildingRequest.setActiveProfileIds(request.getActiveProfiles());
projectBuildingRequest.setInactiveProfileIds(request.getInactiveProfiles());
projectBuildingRequest.setProfiles(request.getProfiles());
projectBuildingRequest.setProcessPlugins(true);
projectBuildingRequest.setBuildStartTime(request.getStartTime());
return projectBuildingRequest;
}
/**
* Suspends current Maven execution context, if any. Returns suspended context or {@code null} if there was no context
* associated with the current thread.
*
* @see #resume(Deque)
* @since 1.5
*/
public static Deque<MavenExecutionContext> suspend() {
Deque<MavenExecutionContext> queue = threadLocal.get();
threadLocal.set(null);
return queue;
}
/**
* Resumes Maven execution context suspended with {@link #suspend()}.
*
* @see #resume(Deque)
* @since 1.5
*/
public static void resume(Deque<MavenExecutionContext> queue) {
if(threadLocal.get() != null) {
throw new IllegalStateException();
}
threadLocal.set(queue);
}
/**
* @since 1.5
*/
@SuppressWarnings("unchecked")
public <T> T getValue(String key) {
return (T) context.get(key);
}
/**
* @since 1.5
*/
public <T> void setValue(String key, T value) {
context.put(key, value);
}
}