blob: ae3e75fcf678aed847767bf727f8c3f97afa54f8 [file] [log] [blame]
package com.intellij.openapi.externalSystem.service;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.externalSystem.ExternalSystemManager;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager;
import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager;
import com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemFacadeWrapper;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.IntegrationKey;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Entry point to work with remote {@link RemoteExternalSystemFacade}.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since 8/8/11 1:08 PM
*/
public class ExternalSystemFacadeManager {
private static final int REMOTE_FAIL_RECOVERY_ATTEMPTS_NUMBER = 3;
private final ConcurrentMap<IntegrationKey, RemoteExternalSystemFacade> myFacadeWrappers = ContainerUtil.newConcurrentMap();
private final Map<IntegrationKey, Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings>> myRemoteFacades
= ContainerUtil.newConcurrentMap();
@NotNull private final Lock myLock = new ReentrantLock();
@NotNull private final RemoteExternalSystemProgressNotificationManager myProgressManager;
@NotNull private final RemoteExternalSystemCommunicationManager myRemoteCommunicationManager;
@NotNull private final InProcessExternalSystemCommunicationManager myInProcessCommunicationManager;
public ExternalSystemFacadeManager(@NotNull ExternalSystemProgressNotificationManager notificationManager,
@NotNull RemoteExternalSystemCommunicationManager remoteCommunicationManager,
@NotNull InProcessExternalSystemCommunicationManager inProcessCommunicationManager)
{
myProgressManager = (RemoteExternalSystemProgressNotificationManager)notificationManager;
myRemoteCommunicationManager = remoteCommunicationManager;
myInProcessCommunicationManager = inProcessCommunicationManager;
}
@NotNull
private static Project findProject(@NotNull IntegrationKey key) {
final ProjectManager projectManager = ProjectManager.getInstance();
for (Project project : projectManager.getOpenProjects()) {
if (key.getIdeProjectName().equals(project.getName()) && key.getIdeProjectLocationHash().equals(project.getLocationHash())) {
return project;
}
}
return projectManager.getDefaultProject();
}
public void onProjectRename(@NotNull String oldName, @NotNull String newName) {
onProjectRename(myFacadeWrappers, oldName, newName);
onProjectRename(myRemoteFacades, oldName, newName);
}
private static <V> void onProjectRename(@NotNull Map<IntegrationKey, V> data,
@NotNull String oldName,
@NotNull String newName)
{
Set<IntegrationKey> keys = ContainerUtilRt.newHashSet(data.keySet());
for (IntegrationKey key : keys) {
if (!key.getIdeProjectName().equals(oldName)) {
continue;
}
IntegrationKey newKey = new IntegrationKey(newName,
key.getIdeProjectLocationHash(),
key.getExternalSystemId(),
key.getExternalProjectConfigPath());
V value = data.get(key);
data.put(newKey, value);
data.remove(key);
if (value instanceof Consumer) {
//noinspection unchecked
((Consumer)value).consume(newKey);
}
}
}
/**
* @return gradle api facade to use
* @throws Exception in case of inability to return the facade
*/
@NotNull
public RemoteExternalSystemFacade getFacade(@Nullable Project project,
@NotNull String externalProjectPath,
@NotNull ProjectSystemId externalSystemId) throws Exception
{
if (project == null) {
project = ProjectManager.getInstance().getDefaultProject();
}
IntegrationKey key = new IntegrationKey(project, externalSystemId, externalProjectPath);
final RemoteExternalSystemFacade facade = myFacadeWrappers.get(key);
if (facade == null) {
final RemoteExternalSystemFacade newFacade = (RemoteExternalSystemFacade)Proxy.newProxyInstance(
ExternalSystemFacadeManager.class.getClassLoader(), new Class[]{RemoteExternalSystemFacade.class, Consumer.class},
new MyHandler(key)
);
myFacadeWrappers.putIfAbsent(key, newFacade);
}
return myFacadeWrappers.get(key);
}
public Object doInvoke(@NotNull IntegrationKey key, @NotNull Project project, Method method, Object[] args, int invocationNumber)
throws Throwable
{
RemoteExternalSystemFacade facade = doGetFacade(key, project);
try {
return method.invoke(facade, args);
}
catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RemoteException && invocationNumber > 0) {
Thread.sleep(1000);
return doInvoke(key, project, method, args, invocationNumber - 1);
}
else {
throw e;
}
}
}
public ExternalSystemCommunicationManager getCommunicationManager(@NotNull ProjectSystemId externalSystemId) {
final boolean currentInProcess = ExternalSystemApiUtil.isInProcessMode(externalSystemId);
return currentInProcess ? myInProcessCommunicationManager : myRemoteCommunicationManager;
}
@SuppressWarnings("ConstantConditions")
@NotNull
private RemoteExternalSystemFacade doGetFacade(@NotNull IntegrationKey key, @NotNull Project project) throws Exception {
final boolean currentInProcess = ExternalSystemApiUtil.isInProcessMode(key.getExternalSystemId());
final ExternalSystemCommunicationManager myCommunicationManager = currentInProcess ? myInProcessCommunicationManager : myRemoteCommunicationManager;
ExternalSystemManager manager = ExternalSystemApiUtil.getManager(key.getExternalSystemId());
if (project.isDisposed() || manager == null) {
return RemoteExternalSystemFacade.NULL_OBJECT;
}
Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings> pair = myRemoteFacades.get(key);
if (pair != null && prepare(myCommunicationManager, project, key, pair)) {
return pair.first;
}
myLock.lock();
try {
pair = myRemoteFacades.get(key);
if (pair != null && prepare(myCommunicationManager, project, key, pair)) {
return pair.first;
}
if (pair != null) {
myFacadeWrappers.clear();
myRemoteFacades.clear();
}
return doCreateFacade(key, project, myCommunicationManager);
}
finally {
myLock.unlock();
}
}
@SuppressWarnings("unchecked")
@NotNull
private RemoteExternalSystemFacade doCreateFacade(@NotNull IntegrationKey key, @NotNull Project project,
@NotNull ExternalSystemCommunicationManager communicationManager) throws Exception {
final RemoteExternalSystemFacade facade = communicationManager.acquire(key.getExternalProjectConfigPath(), key.getExternalSystemId());
if (facade == null) {
throw new IllegalStateException("Can't obtain facade to working with external api at the remote process. Project: " + project);
}
Disposer.register(project, new Disposable() {
@Override
public void dispose() {
myFacadeWrappers.clear();
myRemoteFacades.clear();
}
});
final RemoteExternalSystemFacade result = new ExternalSystemFacadeWrapper(facade, myProgressManager);
ExternalSystemExecutionSettings settings
= ExternalSystemApiUtil.getExecutionSettings(project, key.getExternalProjectConfigPath(), key.getExternalSystemId());
Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings> newPair = Pair.create(result, settings);
myRemoteFacades.put(key, newPair);
result.applySettings(newPair.second);
return result;
}
@SuppressWarnings("unchecked")
private boolean prepare(@NotNull ExternalSystemCommunicationManager communicationManager,
@NotNull Project project, @NotNull IntegrationKey key,
@NotNull Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings> pair)
{
if (!communicationManager.isAlive(pair.first)) {
return false;
}
try {
ExternalSystemExecutionSettings currentSettings
= ExternalSystemApiUtil.getExecutionSettings(project, key.getExternalProjectConfigPath(), key.getExternalSystemId());
if (!currentSettings.equals(pair.second)) {
pair.first.applySettings(currentSettings);
myRemoteFacades.put(key, Pair.create(pair.first, currentSettings));
}
return true;
}
catch (RemoteException e) {
return false;
}
}
public boolean isTaskActive(@NotNull ExternalSystemTaskId id) {
Map<IntegrationKey, Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings>> copy
= ContainerUtilRt.newHashMap(myRemoteFacades);
for (Map.Entry<IntegrationKey, Pair<RemoteExternalSystemFacade, ExternalSystemExecutionSettings>> entry : copy.entrySet()) {
try {
if (entry.getValue().first.isTaskInProgress(id)) {
return true;
}
}
catch (RemoteException e) {
myLock.lock();
try {
myRemoteFacades.remove(entry.getKey());
myFacadeWrappers.remove(entry.getKey());
}
finally {
myLock.unlock();
}
}
}
return false;
}
private class MyHandler implements InvocationHandler {
@NotNull private final AtomicReference<IntegrationKey> myKey = new AtomicReference<IntegrationKey>();
MyHandler(@NotNull IntegrationKey key) {
myKey.set(key);
}
@Nullable
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("consume".equals(method.getName())) {
myKey.set((IntegrationKey)args[0]);
return null;
}
Project project = findProject(myKey.get());
return doInvoke(myKey.get(), project, method, args, REMOTE_FAIL_RECOVERY_ATTEMPTS_NUMBER);
}
}
}