/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.extension.maven.server.core;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import org.eclipse.che.api.core.util.CancellableProcessWrapper;
import org.eclipse.che.api.core.util.ProcessUtil;
import org.eclipse.che.api.core.util.StreamPump;
import org.eclipse.che.api.core.util.Watchdog;
import org.eclipse.che.api.project.server.Project;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.ide.ext.java.server.classpath.ClassPathBuilder;
import org.eclipse.che.ide.ext.java.shared.dto.ClassPathBuilderResult;
import org.eclipse.che.ide.extension.maven.server.projecttype.MavenClassPathConfigurator;
import org.eclipse.che.ide.maven.tools.MavenUtils;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
/**
* Implementation of classpath building for the Maven.
*
* @author Valeriy Svydenko
*/
public class MavenClassPathBuilder implements ClassPathBuilder {
private static final Logger LOG = LoggerFactory.getLogger(MavenClassPathBuilder.class);
private final ExecutorService executorService;
private final ProjectManager projectManager;
private String workspaceId;
@Inject
public MavenClassPathBuilder(ResourcesPlugin resourcesPlugin, ProjectManager projectManager) {
this.projectManager = projectManager;
JavaModelManager.getJavaModelManager().containerInitializersCache.put(MavenClasspathContainer.CONTAINER_ID,
new MavenClasspathContainerInitializer());
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(MavenClassPathBuilder.class.getSimpleName() + "-%d").build();
executorService = Executors.newFixedThreadPool(5, threadFactory);
}
/** {@inheritDoc} */
@Override
public ClassPathBuilderResult buildClassPath(String workspaceId, String projectPath) throws ExecutionException, InterruptedException {
this.workspaceId = workspaceId;
//TODO Temporary solution for IDEX-4270
try {
Project project = projectManager.getProject(workspaceId, projectPath);
if (project != null) {
MavenClassPathConfigurator.configure(project.getBaseFolder());
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
Callable<ClassPathBuilderResult> callable = () -> {
ClassPathBuilderResult result = dependencyUpdateProcessor(projectPath);
IJavaProject javaProject = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(projectPath);
if (ClassPathBuilderResult.Status.SUCCESS.equals(result.getStatus())) {
IClasspathContainer container = MavenClasspathUtil.readMavenClasspath(javaProject);
try {
JavaCore.setClasspathContainer(container.getPath(), new IJavaProject[]{javaProject},
new IClasspathContainer[]{container},
null);
} catch (JavaModelException e) {
LOG.error(e.getMessage(), e);
}
}
return result;
};
return executorService.submit(callable).get();
}
private ClassPathBuilderResult dependencyUpdateProcessor(String projectPath) {
String command = MavenUtils.getMavenExecCommand();
File projectDir = new File(ResourcesPlugin.getPathToWorkspace() + projectPath);
ProcessBuilder classPathProcessBuilder = new ProcessBuilder().command(command, "dependency:build-classpath",
"-Dmdep.outputFile=.codenvy/classpath.maven")
.directory(projectDir)
.redirectErrorStream(true);
ClassPathBuilderResult result = executeBuilderProcess(projectPath, classPathProcessBuilder);
if (ClassPathBuilderResult.Status.SUCCESS.equals(result.getStatus())) {
ProcessBuilder sourcesProcessBuilder = new ProcessBuilder().command(command, "dependency:sources", "-Dclassifier=sources")
.directory(projectDir)
.redirectErrorStream(true);
result = executeBuilderProcess(projectPath, sourcesProcessBuilder);
}
return result;
}
private ClassPathBuilderResult executeBuilderProcess(final String projectPath, ProcessBuilder processBuilder) {
StreamPump output = null;
Watchdog watcher = null;
ClassPathBuilderResult classPathBuilderResult = newDto(ClassPathBuilderResult.class);
int timeout = 10; //10 minutes
int result = -1;
try {
Process process = processBuilder.start();
watcher = new Watchdog("Maven classpath" + "-WATCHDOG", timeout, TimeUnit.MINUTES);
watcher.start(new CancellableProcessWrapper(process,
cancellable -> LOG.warn("Update dependency process has been shutdown "
+ "due to timeout. Project: "
+ projectPath)));
String channel = "dependencyUpdate:output:" + workspaceId + ':' + projectPath;
classPathBuilderResult.setChannel(channel);
BufferOutputFixedRateSender fixedRateSender = new BufferOutputFixedRateSender(channel, 2_000);
output = new StreamPump();
output.start(process, fixedRateSender);
try {
result = process.waitFor();
} catch (InterruptedException e) {
Thread.interrupted(); // we interrupt thread when cancel task
ProcessUtil.kill(process);
}
try {
output.await(); // wait for logger
fixedRateSender.close();
} catch (InterruptedException e) {
Thread.interrupted(); // we interrupt thread when cancel task, NOTE: logs may be incomplete
}
} catch (IOException e) {
LOG.error("", e);
} finally {
if (watcher != null) {
watcher.stop();
}
if (output != null) {
output.stop();
}
}
classPathBuilderResult.setStatus(result == 0 ? ClassPathBuilderResult.Status.SUCCESS : ClassPathBuilderResult.Status.ERROR);
return classPathBuilderResult;
}
@PreDestroy
public void destroy() {
executorService.shutdown();
}
}