/*******************************************************************************
* 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.maven.data.MavenArtifact;
import org.eclipse.che.maven.data.MavenKey;
import org.eclipse.che.maven.data.MavenWorkspaceCache;
import org.eclipse.che.maven.server.MavenTerminal;
import org.eclipse.che.plugin.maven.server.MavenServerManager;
import org.eclipse.che.plugin.maven.server.MavenServerWrapper;
import org.eclipse.che.plugin.maven.server.MavenWrapperManager;
import org.eclipse.che.plugin.maven.server.core.project.MavenProject;
import org.eclipse.che.plugin.maven.server.core.project.MavenProjectModifications;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
/**
* Holds all maven projects in workspace
*
* @author Evgen Vidolob
*/
@Singleton
public class MavenProjectManager {
private final MavenWorkspaceCache mavenWorkspaceCache;
private final Map<MavenKey, MavenProject> keyToProjectMap;
private final Map<IProject, MavenProject> projectToMavenProjectMap;
private final Map<MavenProject, List<MavenProject>> parentToModulesMap;
private final Map<MavenProject, MavenProject> moduleToParentMap;
private final List<MavenProjectListener> listeners = new CopyOnWriteArrayList<>();
//project that does not have parent project in our workspace
private final List<MavenProject> rootProjects;
private final MavenWrapperManager wrapperManager;
private final MavenServerManager serverManager;
private final MavenTerminal terminal;
private final MavenProgressNotifier mavenNotifier;
private final Provider<IWorkspace> workspaceProvider;
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private final MavenProjectListener dispatcher;
@Inject
public MavenProjectManager(MavenWrapperManager wrapperManager,
MavenServerManager serverManager,
MavenTerminal terminal,
MavenProgressNotifier mavenNotifier,
EclipseWorkspaceProvider workspaceProvider) {
this.wrapperManager = wrapperManager;
this.serverManager = serverManager;
this.terminal = terminal;
this.mavenNotifier = mavenNotifier;
this.workspaceProvider = workspaceProvider;
mavenWorkspaceCache = new MavenWorkspaceCache();
keyToProjectMap = new HashMap<>();
projectToMavenProjectMap = new HashMap<>();
parentToModulesMap = new HashMap<>();
dispatcher = createListenersDispatcher();
moduleToParentMap = new HashMap<>();
rootProjects = new ArrayList<>();
}
private MavenProjectListener createListenersDispatcher() {
return (MavenProjectListener)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[] {MavenProjectListener.class},
(proxy, method, args) -> {
for (MavenProjectListener listener : listeners) {
method.invoke(listener, args);
}
return null;
});
}
public void addListener(MavenProjectListener listener) {
listeners.add(listener);
}
public void removeListener(MavenProjectListener listener) {
listeners.remove(listener);
}
public void resolveMavenProject(IProject project, MavenProject mavenProject) {
MavenServerWrapper mavenServer = wrapperManager.getMavenServer(MavenWrapperManager.ServerType.RESOLVE);
try {
mavenNotifier.setText("Resolving project: " + mavenProject.getName());
mavenServer.customize(copyWorkspaceCache(), terminal, mavenNotifier, false, true);
MavenProjectModifications modifications = mavenProject.resolve(project, mavenServer, serverManager);
dispatcher.projectResolved(mavenProject, modifications);
} finally {
wrapperManager.release(mavenServer);
}
}
public void update(List<IProject> projects, boolean recursive) {
if (projects.isEmpty()) {
return;
}
mavenNotifier.start();
UpdateState state = new UpdateState();
Deque<MavenProject> stack = new LinkedList<>();
for (IProject project : projects) {
MavenProject mavenProject = findMavenProject(project);
if (mavenProject != null) {
internalUpdate(mavenProject, findParentProject(mavenProject), false, recursive, state, stack);
} else {
internalAddMavenProject(project, recursive, state, stack);
}
}
mavenNotifier.stop();
state.fireUpdate();
}
public MavenProject findParentProject(MavenProject mavenProject) {
readLock.lock();
try {
return moduleToParentMap.get(mavenProject);
} finally {
readLock.unlock();
}
}
private void internalAddMavenProject(IProject project, boolean recursive, UpdateState state, Deque<MavenProject> stack) {
MavenProject mavenProject = new MavenProject(project, workspaceProvider.get());
MavenProject potentialParent = null;
for (MavenProject parent : getAllProjects()) {
if (parent.containsAsModule(project.getFullPath())) {
potentialParent = parent;
break;
}
}
internalUpdate(mavenProject, potentialParent, true, recursive, state, stack);
}
private void internalUpdate(MavenProject mavenProject, MavenProject parentProject, boolean isNew, boolean recursive, UpdateState state,
Deque<MavenProject> stack) {
if (stack.contains(mavenProject)) {
return; //recursion
}
stack.addFirst(mavenProject);
mavenNotifier.setText("Reading pom: " + mavenProject.getPomPath());
List<MavenProject> oldModules = findModules(mavenProject);
Set<MavenProject> oldChilds = new HashSet<>();
if (!isNew) {
oldChilds.addAll(findChildProjects(mavenProject));
}
writeLock.lock();
try {
if (!isNew) {
clearMavenKeyMap(mavenProject);
}
} finally {
writeLock.unlock();
}
MavenProjectModifications modifications = new MavenProjectModifications();
//re read maven project meta info from pom.xml
modifications = modifications.addChanges(mavenProject.read(serverManager));
writeLock.lock();
try {
fillMavenKeyMap(mavenProject);
projectToMavenProjectMap.put(mavenProject.getProject(), mavenProject);
} finally {
writeLock.unlock();
}
if (isNew) {
addToChild(parentProject, mavenProject);
} else {
updateChild(parentProject, mavenProject);
}
// if (hasparent) {
state.addUpdate(mavenProject, modifications);
// }
List<IProject> modules = mavenProject.getModulesProjects();
List<MavenProject> removedModules =
oldModules.stream()
.filter(oldModule -> !modules.contains(oldModule.getProject()))
.collect(Collectors.toList());
for (MavenProject removedModule : removedModules) {
removeModule(mavenProject, removedModule);
internalDelete(mavenProject, removedModule, state);
oldChilds.removeAll(state.removedProjects);
}
for (IProject module : modules) {
MavenProject project = findMavenProject(module);
boolean isNewProject = project == null;
if (isNewProject) {
project = new MavenProject(module, workspaceProvider.get());
} else {
MavenProject parent = findParentProject(project);
if (parent != null && parent != mavenProject) {
//TODO add log
continue;
}
}
if (isNewProject || recursive) {
internalUpdate(project, mavenProject, isNewProject, recursive, state, stack);
} else {
if (updateChild(mavenProject, project)) {
state.addUpdate(project, new MavenProjectModifications());
}
}
}
oldChilds.addAll(findChildProjects(mavenProject));
for (MavenProject oldModule : oldChilds) {
internalUpdate(oldModule, findParentProject(oldModule), false, false, state, stack);
}
stack.pop();
}
private void internalDelete(MavenProject parentProject, MavenProject removedModule, UpdateState state) {
for (MavenProject project : findModules(removedModule)) {
internalDelete(removedModule, project, state);
}
writeLock.lock();
try {
if (parentProject == null) {
rootProjects.remove(removedModule);
} else {
removeModule(parentProject, removedModule);
}
projectToMavenProjectMap.remove(removedModule.getProject());
clearMavenKeyMap(removedModule);
moduleToParentMap.remove(removedModule);
parentToModulesMap.remove(removedModule);
} finally {
writeLock.unlock();
}
state.remove(removedModule);
}
private boolean updateChild(MavenProject parentProject, MavenProject module) {
MavenProject oldParent = findParentProject(module);
if (oldParent == parentProject) {
return false;
}
writeLock.lock();
try {
if (oldParent == null) {
rootProjects.remove(module);
} else {
removeModule(oldParent, module);
}
if (parentProject == null) {
rootProjects.add(module);
} else {
addModule(parentProject, module);
}
} finally {
writeLock.unlock();
}
return false;
}
private void removeModule(MavenProject oldParent, MavenProject module) {
writeLock.lock();
try {
List<MavenProject> modules = parentToModulesMap.get(oldParent);
if (modules != null) {
modules.remove(module);
moduleToParentMap.remove(module);
}
} finally {
writeLock.unlock();
}
}
private void addToChild(MavenProject parentProject, MavenProject module) {
writeLock.lock();
try {
if (parentProject == null) {
rootProjects.add(module);
} else {
addModule(parentProject, module);
}
} finally {
writeLock.unlock();
}
}
private void addModule(MavenProject parentProject, MavenProject module) {
writeLock.lock();
try {
List<MavenProject> modules = parentToModulesMap.get(parentProject);
if (modules == null) {
modules = new ArrayList<>();
parentToModulesMap.put(parentProject, modules);
}
modules.add(module);
moduleToParentMap.put(module, parentProject);
} finally {
writeLock.unlock();
}
}
public List<MavenProject> getAllProjects() {
readLock.lock();
try {
return new ArrayList<>(projectToMavenProjectMap.values());
} finally {
readLock.unlock();
}
}
public MavenProject getMavenProject(IProject iProject) {
readLock.lock();
try {
return projectToMavenProjectMap.get(iProject);
} finally {
readLock.unlock();
}
}
public MavenProject getMavenProject(String projectPath) {
final IProject project = workspaceProvider.get().getRoot().getProject(projectPath);
return getMavenProject(project);
}
private void fillMavenKeyMap(MavenProject mavenProject) {
MavenKey mavenKey = mavenProject.getMavenKey();
mavenWorkspaceCache.put(mavenKey, mavenProject.getPomFile());
keyToProjectMap.put(mavenKey, mavenProject);
}
private void clearMavenKeyMap(MavenProject mavenProject) {
MavenKey mavenKey = mavenProject.getMavenKey();
mavenWorkspaceCache.invalidate(mavenKey);
keyToProjectMap.remove(mavenKey);
}
private List<MavenProject> findChildProjects(MavenProject mavenProject) {
readLock.lock();
try {
MavenKey parentKey = mavenProject.getMavenKey();
return projectToMavenProjectMap.values().stream()
.filter(project -> mavenProject != project)
.filter(project -> project.getParentKey().equals(parentKey))
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public List<MavenProject> findModules(MavenProject parent) {
readLock.lock();
try {
List<MavenProject> modules = parentToModulesMap.get(parent);
if (modules == null) {
modules = Collections.emptyList();
}
return new ArrayList<>(modules);
} finally {
readLock.unlock();
}
}
public MavenProject findMavenProject(IProject project) {
readLock.lock();
try {
return projectToMavenProjectMap.get(project);
} finally {
readLock.unlock();
}
}
public MavenWorkspaceCache copyWorkspaceCache() {
readLock.lock();
try {
return mavenWorkspaceCache.copy();
} finally {
readLock.unlock();
}
}
public List<MavenProject> findDependentProjects(List<MavenProject> projects) {
readLock.lock();
try {
List<MavenProject> result = new ArrayList<>();
Set<MavenKey> mavenKeys = projects.stream().map(MavenProject::getMavenKey).collect(Collectors.toSet());
Set<String> paths = projects.stream()
.map(project -> project.getProject().getFullPath().toOSString())
.collect(Collectors.toSet());
for (MavenProject project : projectToMavenProjectMap.values()) {
boolean isAdd = false;
for (String path : project.getModulesPath()) {
if (paths.contains(path)) {
isAdd = true;
break;
}
}
if (!isAdd) {
for (MavenArtifact artifact : project.getDependencies()) {
if (contains(mavenKeys, artifact.getArtifactId(), artifact.getGroupId(), artifact.getVersion())) {
isAdd = true;
break;
}
}
}
if (isAdd) {
result.add(project);
}
}
return result;
} finally {
readLock.unlock();
}
}
private boolean contains(Set<MavenKey> mavenKeys, String artifactId, String groupId, String version) {
return mavenKeys.stream().filter(key -> Objects.equals(key.getArtifactId(), artifactId)
&& Objects.equals(key.getGroupId(), groupId)
&& Objects.equals(key.getVersion(), version))
.findFirst().isPresent();
}
public void delete(List<IProject> projects) {
if (projects.isEmpty()) {
return;
}
UpdateState state = new UpdateState();
Deque<MavenProject> stack = new LinkedList<>();
Set<MavenProject> childToUpdate = new HashSet<>();
for (IProject project : projects) {
MavenProject mavenProject = findMavenProject(project);
if (mavenProject == null) {
return;
}
childToUpdate.addAll(findChildProjects(mavenProject));
internalDelete(findParentProject(mavenProject), mavenProject, state);
}
childToUpdate.removeAll(state.removedProjects);
for (MavenProject mavenProject : childToUpdate) {
internalUpdate(mavenProject, null, false, false, state, stack);
}
state.fireUpdate();
}
private class UpdateState {
Map<MavenProject, MavenProjectModifications> projectWithModification = new HashMap<>();
Set<MavenProject> removedProjects = new HashSet<>();
public void addUpdate(MavenProject mavenProject, MavenProjectModifications modifications) {
removedProjects.remove(mavenProject);
projectWithModification.put(mavenProject, modifications);
}
public void remove(MavenProject mavenProject) {
projectWithModification.remove(mavenProject);
removedProjects.add(mavenProject);
}
public void fireUpdate() {
if (projectWithModification.isEmpty() && removedProjects.isEmpty()) {
return;
}
Map<MavenProject, MavenProjectModifications> modified = new HashMap<>(projectWithModification);
List<MavenProject> removed = new ArrayList<>(removedProjects);
dispatcher.projectUpdated(modified, removed);
}
}
}