/*******************************************************************************
* Copyright (c) 2012-2017 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.plugin.maven.server.core;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.project.server.ProjectDeletedEvent;
import org.eclipse.che.api.project.server.ProjectRegistry;
import org.eclipse.che.api.project.server.RegisteredProject;
import org.eclipse.che.ide.ext.java.shared.Constants;
import org.eclipse.che.jdt.core.launching.JREContainerInitializer;
import org.eclipse.che.plugin.maven.server.core.classpath.ClasspathHelper;
import org.eclipse.che.plugin.maven.server.core.classpath.ClasspathManager;
import org.eclipse.che.plugin.maven.server.core.project.MavenProject;
import org.eclipse.che.plugin.maven.server.core.project.MavenProjectModifications;
import org.eclipse.che.plugin.maven.shared.MavenAttributes;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.JavaModelStatus;
import org.jdom.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import static org.eclipse.che.plugin.maven.shared.MavenAttributes.MAVEN_ID;
/**
* @author Evgen Vidolob
*/
@Singleton
public class MavenWorkspace {
private static final Logger LOG = LoggerFactory.getLogger(MavenWorkspace.class);
private final MavenProjectManager manager;
private final Provider<ProjectRegistry> projectRegistryProvider;
private final MavenCommunication communication;
private final ClasspathManager classpathManager;
private MavenTaskExecutor resolveExecutor;
private MavenTaskExecutor classPathExecutor;
private Set<MavenProject> projectsToResolve = new CopyOnWriteArraySet<>();
@Inject
public MavenWorkspace(MavenProjectManager manager,
MavenProgressNotifier notifier,
MavenExecutorService executorService,
Provider<ProjectRegistry> projectRegistryProvider,
MavenCommunication communication,
ClasspathManager classpathManager,
EventService eventService,
EclipseWorkspaceProvider workspaceProvider) {
this.projectRegistryProvider = projectRegistryProvider;
this.communication = communication;
this.classpathManager = classpathManager;
this.manager = manager;
resolveExecutor = new MavenTaskExecutor(executorService, notifier);
eventService.subscribe(new EventSubscriber<ProjectDeletedEvent>() {
@Override
public void onEvent(ProjectDeletedEvent event) {
IProject project = workspaceProvider.get().getRoot().getProject(event.getProjectPath());
manager.delete(Collections.singletonList(project));
}
});
manager.addListener(new MavenProjectListener() {
@Override
public void projectResolved(MavenProject project, MavenProjectModifications modifications) {
// communication.sendUpdateMassage(Collections.emptySet(), Collections.emptyList());
}
@Override
public void projectUpdated(Map<MavenProject, MavenProjectModifications> updated,
List<MavenProject> removed) {
removeProjects(removed);
createNewProjects(updated.keySet());
List<MavenProject> allChangedProjects = new ArrayList<>(updated.keySet().size() + removed.size());
allChangedProjects.addAll(updated.keySet());
allChangedProjects.addAll(removed);
List<MavenProject> needResolve = manager.findDependentProjects(allChangedProjects);
needResolve.addAll(updated.keySet());
addResolveProjects(needResolve);
communication.sendUpdateMassage(updated.keySet(), removed);
}
});
}
private void addResolveProjects(List<MavenProject> needResolve) {
projectsToResolve.addAll(needResolve);
}
private void createNewProjects(Set<MavenProject> mavenProjects) {
mavenProjects.stream()
.forEach(project -> {
try {
String path = project.getProject().getFullPath().toOSString();
projectRegistryProvider.get().setProjectType(path, MAVEN_ID, false);
} catch (ConflictException | ServerException | NotFoundException e) {
LOG.error("Can't add new project: " + project.getProject().getFullPath(), e);
}
});
mavenProjects.forEach(this::updateJavaProject);
}
private void removeProjects(List<MavenProject> removed) {
removed.forEach(project -> {
try {
projectRegistryProvider.get().removeProjectType(project.getProject().getFullPath().toOSString(), MAVEN_ID);
} catch (ServerException | ForbiddenException | ConflictException | NotFoundException e) {
LOG.error(e.getMessage(), e);
}
});
}
public void update(List<IProject> projects) {
manager.update(projects, true);
runResolve();
}
private void runResolve() {
//TODO synchronise on projectsToResolve change
Set<MavenProject> needResolve = new HashSet<>(projectsToResolve);
projectsToResolve.clear();
for (MavenProject mavenProject : needResolve) {
resolveExecutor.submitTask(new MavenProjectResolveTask(mavenProject, manager, () -> {
addSourcesFromBuildHelperPlugin(mavenProject);
classpathManager.updateClasspath(mavenProject);
}));
}
}
private void updateJavaProject(MavenProject project) {
IJavaProject javaProject = JavaCore.create(project.getProject());
try {
ClasspathHelper helper = new ClasspathHelper(javaProject);
project.getSources().stream().map(s -> project.getProject().getFullPath().append(s)).forEach(helper::addSourceEntry);
project.getTestSources().stream().map(s -> project.getProject().getFullPath().append(s)).forEach(helper::addSourceEntry);
//add maven classpath container
helper.addContainerEntry(new Path(MavenClasspathContainer.CONTAINER_ID));
//add JRE classpath container
helper.addContainerEntry(new Path(JREContainerInitializer.JRE_CONTAINER));
javaProject.setRawClasspath(helper.getEntries(), null);
} catch (JavaModelException e) {
LOG.error("Can't update Java project classpath", e);
}
}
private void addSourcesFromBuildHelperPlugin(MavenProject project) {
IJavaProject javaProject = JavaCore.create(project.getProject());
try {
ClasspathHelper helper = new ClasspathHelper(javaProject);
Element pluginConfigurationSource =
project.getPluginConfiguration("org.codehaus.mojo", "build-helper-maven-plugin", "add-source");
Element pluginConfigurationTestSource =
project.getPluginConfiguration("org.codehaus.mojo", "build-helper-maven-plugin", "add-test-source");
IPath projectPath = project.getProject().getFullPath();
RegisteredProject registeredProject = projectRegistryProvider.get().getProject(projectPath.toOSString());
if (registeredProject == null) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.CORE_EXCEPTION,
"Project " + projectPath.toOSString() + " doesn't exist"));
}
List<String> sourceFolders = registeredProject.getAttributes().get(Constants.SOURCE_FOLDER);
List<String> testSourceFolders = registeredProject.getAttributes().get(MavenAttributes.TEST_SOURCE_FOLDER);
addSourcePathFromConfiguration(helper, project, pluginConfigurationSource, sourceFolders);
addSourcePathFromConfiguration(helper, project, pluginConfigurationTestSource, testSourceFolders);
javaProject.setRawClasspath(helper.getEntries(), null);
} catch (JavaModelException e) {
LOG.error("Can't update Java project classpath with Maven build helper plugin configuration", e);
}
}
private void addSourcePathFromConfiguration(ClasspathHelper helper,
MavenProject project,
Element configuration,
List<String> attributes) {
if (configuration != null) {
Element sources = configuration.getChild("sources");
if (sources != null) {
for (Object element : sources.getChildren()) {
final String path = ((Element)element).getTextTrim();
final IPath projectLocation = project.getProject().getLocation();
final String projectPath = projectLocation.toOSString();
final String sourceFolder = path.contains(projectPath) ? path.substring(projectPath.length() + 1) : path;
helper.addSourceEntry(project.getProject().getFullPath().append(sourceFolder));
if (!attributes.contains(sourceFolder)) {
attributes.add(sourceFolder);
}
}
}
}
}
/**
* Waits for resolving tasks ends.
* For test only.
*/
public void waitForUpdate() {
resolveExecutor.waitForEndAllTasks();
}
}