| 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); |
| } |
| } |
| } |