com.headwire.aem.tooling.intellij.communication.ServerConnectionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.headwire.aem.tooling.intellij.communication.ServerConnectionManager.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.headwire.aem.tooling.intellij.communication;

import com.headwire.aem.tooling.intellij.action.ProgressHandler;
import com.headwire.aem.tooling.intellij.action.ProgressHandlerImpl;
import com.headwire.aem.tooling.intellij.config.ModuleManager;
import com.headwire.aem.tooling.intellij.config.UnifiedModule;
import com.headwire.aem.tooling.intellij.config.ServerConfiguration;
import com.headwire.aem.tooling.intellij.config.ServerConfigurationManager;

//AS TODO: We should use Eclipse Stuff here -> find a way to make this IDE independent
import com.headwire.aem.tooling.intellij.eclipse.ServerUtil;
import com.headwire.aem.tooling.intellij.eclipse.stub.CoreException;

//AS TODO: We should use Eclipse Stuff here -> find a way to make this IDE independent
import com.headwire.aem.tooling.intellij.eclipse.stub.IServer;
import com.headwire.aem.tooling.intellij.eclipse.stub.NullProgressMonitor;

import com.headwire.aem.tooling.intellij.explorer.RunExecutionMonitor;
import com.headwire.aem.tooling.intellij.explorer.SlingServerTreeSelectionHandler;
import com.headwire.aem.tooling.intellij.util.BundleStateHelper;
import com.headwire.aem.tooling.intellij.util.ComponentProvider;
import com.headwire.aem.tooling.intellij.util.Util;
import com.intellij.execution.ExecutionManager;
import com.intellij.execution.Executor;
import com.intellij.execution.ExecutorRegistry;
import com.intellij.execution.KillableProcess;
import com.intellij.execution.RunManagerEx;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.impl.RunManagerImpl;
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.remote.RemoteConfiguration;
import com.intellij.execution.remote.RemoteConfigurationType;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.CompileStatusNotification;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import org.apache.commons.io.IOUtils;
import org.apache.sling.ide.artifacts.EmbeddedArtifact;
import org.apache.sling.ide.artifacts.EmbeddedArtifactLocator;
import org.apache.sling.ide.eclipse.core.internal.Activator;
import org.apache.sling.ide.io.ConnectorException;
import org.apache.sling.ide.osgi.OsgiClient;
import org.apache.sling.ide.osgi.OsgiClientException;
import org.apache.sling.ide.serialization.SerializationException;
import org.apache.sling.ide.transport.Command;
import org.apache.sling.ide.transport.Repository;
import org.apache.sling.ide.transport.RepositoryException;
import org.apache.sling.ide.transport.RepositoryInfo;
import org.apache.sling.ide.transport.ResourceProxy;
import org.apache.sling.ide.transport.Result;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.execution.MavenRunConfigurationType;
import org.jetbrains.idea.maven.execution.MavenRunnerParameters;
import org.jetbrains.idea.maven.model.MavenExplicitProfiles;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenDataKeys;
import org.jetbrains.idea.maven.utils.actions.MavenActionUtil;
import org.osgi.framework.Version;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static com.headwire.aem.tooling.intellij.config.ServerConfiguration.Module;
import static com.headwire.aem.tooling.intellij.util.Constants.JCR_ROOT_FOLDER_NAME;
import static com.headwire.aem.tooling.intellij.util.ExecutionUtil.WaitableRunner;
import static com.headwire.aem.tooling.intellij.util.ExecutionUtil.runAndWait;

/**
 * Handles the Server Connections for the Plugin, its state and flags
 *
 * Created by Andreas Schaefer (Headwire.com) on 5/21/15.
 */
public class ServerConnectionManager extends AbstractProjectComponent {

    private static List<ServerConfiguration.ServerStatus> CONFIGURATION_CHECKED = Arrays
            .asList(ServerConfiguration.ServerStatus.checking, ServerConfiguration.ServerStatus.running);

    private static List<ServerConfiguration.ServerStatus> CONFIGURATION_IN_USE = Arrays.asList(
            ServerConfiguration.ServerStatus.connecting, ServerConfiguration.ServerStatus.connected,
            ServerConfiguration.ServerStatus.disconnecting, ServerConfiguration.ServerStatus.checking,
            ServerConfiguration.ServerStatus.running);

    private SlingServerTreeSelectionHandler selectionHandler;
    private MessageManager messageManager;
    private ServerConfigurationManager serverConfigurationManager;
    //    private NewResourceChangeCommandFactory commandFactory;
    private IntelliJDeploymentManager deploymentManager;
    private ModuleManager moduleManager;

    private static boolean firstRun = true;

    public ServerConnectionManager(@NotNull Project project) {
        super(project);
        messageManager = ComponentProvider.getComponent(myProject, MessageManager.class);
        serverConfigurationManager = ComponentProvider.getComponent(myProject, ServerConfigurationManager.class);
        deploymentManager = new IntelliJDeploymentManager(project);
        moduleManager = ComponentProvider.getComponent(myProject, ModuleManager.class);
    }

    public void init(@NotNull SlingServerTreeSelectionHandler slingServerTreeSelectionHandler) {
        selectionHandler = slingServerTreeSelectionHandler;
    }

    // ----- Server State Flags

    public boolean isConfigurationEditable() {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        return serverConfiguration != null && !CONFIGURATION_IN_USE.contains(serverConfiguration.getServerStatus());
    }

    public boolean isConnectionInUse() {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        return serverConfiguration != null && CONFIGURATION_IN_USE.contains(serverConfiguration.getServerStatus());
    }

    public boolean isConnectionIsStoppable() {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        return serverConfiguration != null && CONFIGURATION_IN_USE.contains(serverConfiguration.getServerStatus());
    }

    public boolean isConnectionNotInUse() {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        return serverConfiguration != null && !CONFIGURATION_IN_USE.contains(serverConfiguration.getServerStatus());
    }

    public boolean isConfigurationSelected() {
        return selectionHandler.getCurrentConfiguration() != null;
    }

    public void checkModules(OsgiClient osgiClient) {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        if (serverConfiguration != null) {
            ServerConfiguration.SynchronizationStatus status = ServerConfiguration.SynchronizationStatus.upToDate;
            boolean allSynchronized = true;
            if (checkBinding(serverConfiguration, new ProgressHandlerImpl("Check Bindings"))) {
                int moduleCount = serverConfiguration.getModuleList().size();
                float steps = (float) (0.9 / moduleCount);
                for (Module module : serverConfiguration.getModuleList()) {
                    boolean sync = checkModule(osgiClient, module);
                    // Any out of sync marks the project out of sync
                    if (!sync) {
                        status = ServerConfiguration.SynchronizationStatus.outdated;
                    }
                }
            } else {
                status = ServerConfiguration.SynchronizationStatus.failed;
            }
            updateStatus(serverConfiguration.getName(), status);
        } else {
            messageManager.sendNotification("deploy.module.no.configuration.selected", NotificationType.WARNING);
        }
    }

    public boolean checkModule(@NotNull OsgiClient osgiClient, @NotNull final Module module) {
        boolean ret = true;
        try {
            if (module.isPartOfBuild()) {
                // Check Binding
                if (checkBinding(module.getParent(), new ProgressHandlerImpl("Check Bindings"))) {
                    UnifiedModule unifiedModule = module.getUnifiedModule();
                    if (unifiedModule != null) {
                        String moduleName = unifiedModule.getName();
                        String symbolicName = unifiedModule.getSymbolicName();
                        String version = unifiedModule.getVersion();
                        version = checkBundleVersion(version);
                        updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.checking);
                        if (module.isOSGiBundle()) {
                            //AS TODO: This looks like OSGi Symbolic Name
                            Version remoteVersion = osgiClient.getBundleVersion(module.getSymbolicName());
                            // If not remote Version is found there is a chance that the Felix Bundle did change
                            // the symbolic names and here we test that and inform the user about it
                            if (remoteVersion == null) {
                                // If this is Maven and the last part of the package is also used at the beginning of the Artifact Id
                                if (unifiedModule.isMavenBased() && !module.isIgnoreSymbolicNameMismatch()) {
                                    MavenProject mavenProject = moduleManager.getMavenProject(unifiedModule);
                                    if (mavenProject != null) {
                                        String groupId = mavenProject.getMavenId().getGroupId();
                                        String artifactId = mavenProject.getMavenId().getArtifactId();
                                        int index = groupId.lastIndexOf('.');
                                        String lastPackage = groupId.substring(index > 0 ? index + 1 : 0);
                                        if (artifactId.startsWith(lastPackage)) {
                                            WaitableRunner<AtomicInteger> runner = new WaitableRunner<AtomicInteger>() {
                                                private AtomicInteger response = new AtomicInteger(1);

                                                @Override
                                                public boolean isAsynchronous() {
                                                    return true;
                                                }

                                                @Override
                                                public AtomicInteger getResponse() {
                                                    return response;
                                                }

                                                @Override
                                                public void run() {
                                                    int selection = messageManager.showAlertWithOptions(
                                                            NotificationType.WARNING,
                                                            "module.check.possible.symbolic.name.mismatch",
                                                            module.getSymbolicName());
                                                    getResponse().set(selection);
                                                }
                                            };
                                            com.headwire.aem.tooling.intellij.util.ExecutionUtil.runAndWait(runner);
                                            if (runner.getResponse().get() == 0) {
                                                // If ignore is selected then save it on that moduleL
                                                module.setIgnoreSymbolicNameMismatch(true);
                                            }
                                        }
                                    }
                                }
                            }
                            Version localVersion = new Version(version);
                            messageManager.sendDebugNotification("debug.check.osgi.module", moduleName,
                                    symbolicName, remoteVersion, localVersion);
                            boolean moduleUpToDate = remoteVersion != null
                                    && remoteVersion.compareTo(localVersion) >= 0;
                            Object state = BundleStateHelper.getBundleState(module);
                            messageManager.sendDebugNotification("debug.bundle.module.state", module.getName(),
                                    state);
                            if (remoteVersion == null) {
                                // Mark as not deployed yet
                                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.notDeployed);
                                ret = false;
                            } else if (moduleUpToDate) {
                                // Mark as synchronized
                                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.upToDate);
                            } else {
                                // Mark as out of date
                                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.outdated);
                                ret = false;
                            }
                        } else if (module.isSlingPackage()) {
                            long lastModificationTimestamp = getLastModificationTimestamp(module);
                            long moduleModificationTimestamp = module.getLastModificationTimestamp();
                            if (lastModificationTimestamp > moduleModificationTimestamp) {
                                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.outdated);
                                ret = false;
                            } else {
                                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.upToDate);
                            }
                        } else {
                            updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.unsupported);
                        }
                    } else {
                        updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
                        ret = false;
                    }
                } else {
                    updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
                    ret = false;
                }
            } else {
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.excluded);
            }
        } catch (OsgiClientException e1) {
            // Mark connection as failed
            updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
            ret = false;
        }
        return ret;
    }

    private String checkBundleVersion(String version) {
        String ret = "";
        // Versions need to be in this format n.n.n(-|_)aaaaa where n is a number and a are alphanumeric characters
        // If a version number is missing we need to add and superfluous numbers need to be removed
        int separator = version.indexOf('-');
        if (separator < 0) {
            separator = version.indexOf("-");
        }
        String qualifier = "";
        if (separator >= 0) {
            qualifier = "." + (separator < version.length() - 1 ? version.substring(separator + 1) : "");
            version = version.substring(0, separator);
        }
        String[] tokens = version.split("\\.");
        ArrayList<String> tokenList = new ArrayList<String>(Arrays.asList(tokens));
        while (tokenList.size() < 3) {
            tokenList.add("0");
        }
        while (tokenList.size() > 3) {
            tokenList.remove(tokenList.size() - 1);
        }
        // Build version
        for (String token : tokenList) {
            ret += token + ".";
        }
        ret = ret.substring(0, ret.length() - 1);
        ret += qualifier;

        return ret;
    }

    /**
     * Binding is the process of connecting the Project's Modules with the Maven Modules (its sub projects)
     *
     * @param serverConfiguration The Server Connection that is checked and bound if not already done
     * @return True if the the connection was successfully bound otherwise false
     */
    public boolean checkBinding(@NotNull ServerConfiguration serverConfiguration,
            final ProgressHandler progressHandler) {
        boolean ret = true;
        if (!serverConfiguration.isBound()) {
            ret = findUnboundModules(serverConfiguration).isEmpty();
        }
        return ret;
    }

    public List<Module> findUnboundModules(@NotNull ServerConfiguration serverConfiguration) {
        List<UnifiedModule> unifiedModules = moduleManager.getUnifiedModules(serverConfiguration);
        List<Module> moduleList = new ArrayList<Module>(serverConfiguration.getModuleList());
        for (UnifiedModule unifiedModule : unifiedModules) {
            Module moduleFound = null;
            for (Module module : moduleList) {
                if (unifiedModule.containsServerConfigurationModule(module)) {
                    moduleFound = module;
                    break;
                }
            }
            if (moduleFound != null) {
                moduleList.remove(moduleFound);
            }
        }
        return moduleList;
    }

    //    /**
    //     * Binds found Modules and returns a list of modules not found
    //     * @param serverConfiguration
    //     * @param progressHandler
    //     * @return List of all modules found that did not have a matching Nodule Context and so are abandoned
    //     */
    //    public List<Module> bindModules(@NotNull ServerConfiguration serverConfiguration, final ProgressHandler progressHandler) {
    //        List<UnifiedModule> moduleContexts = ComponentProvider.getComponent(ModuleManager.class).getModuleManagerInstance(myProject, serverConfiguration).getUnifiedModules();
    //        List<Module> moduleList = new ArrayList<Module>(serverConfiguration.getModuleList());
    //        ProgressHandler progressHandlerSubTask = progressHandler.startSubTasks(moduleContexts.size(), "Bind Modules");
    //        for(UnifiedModule moduleContext : moduleContexts) {
    //            progressHandlerSubTask.next("Bind Module: " + moduleContext.getName());
    //            String moduleName = moduleContext.getName();
    //            String symbolicName = moduleContext.getSymbolicName();
    //            String version = moduleContext.getVersion();
    //            // Check if this Module is listed in the Module Sub Tree of the Configuration. If not add it.
    //            messageManager.sendDebugNotification("Check Binding for Maven Module: '" + moduleName + "', symbolic name: '" + symbolicName + "', version: '" + version + "'");
    //            // Ignore the Unnamed Projects
    //            if(moduleName == null) {
    //                continue;
    //            }
    //            ServerConfiguration.Module module = serverConfiguration.obtainModuleBySymbolicName(ServerConfiguration.Module.getSymbolicName(moduleContext));
    //            if(module == null) {
    //                module = serverConfiguration.addModule(myProject, moduleContext);
    //            } else if(!module.isBound()) {
    //                // If the module already exists then it could be from the Storage so we need to re-bind with the maven project
    //                module.rebind(myProject, moduleContext);
    //                moduleList.remove(module);
    //            } else {
    //                moduleList.remove(module);
    //            }
    //        }
    //        return moduleList;
    //    }

    public enum BundleStatus {
        upToDate, outDated, failed
    };

    public BundleStatus checkAndUpdateSupportBundle(boolean onlyCheck) {
        BundleStatus ret = BundleStatus.failed;

        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        if (serverConfiguration != null) {
            try {
                OsgiClient osgiClient = obtainSGiClient();
                EmbeddedArtifactLocator artifactLocator = ComponentProvider.getComponent(myProject,
                        EmbeddedArtifactLocator.class);
                if (artifactLocator != null) {
                    Version remoteVersion = osgiClient
                            .getBundleVersion(EmbeddedArtifactLocator.SUPPORT_BUNDLE_SYMBOLIC_NAME);

                    messageManager.sendInfoNotification("remote.repository.version.installed.support.bundle",
                            remoteVersion);

                    final EmbeddedArtifact supportBundle = artifactLocator.loadToolingSupportBundle();
                    final Version embeddedVersion = new Version(supportBundle.getVersion());

                    if (remoteVersion == null || remoteVersion.compareTo(embeddedVersion) < 0) {
                        ret = BundleStatus.outDated;
                        if (!onlyCheck) {
                            InputStream contents = null;
                            try {
                                messageManager.sendInfoNotification(
                                        "remote.repository.begin.installing.support.bundle", embeddedVersion);
                                contents = supportBundle.openInputStream();
                                osgiClient.installBundle(contents, supportBundle.getName());
                                ret = BundleStatus.upToDate;
                            } finally {
                                IOUtils.closeQuietly(contents);
                            }
                            remoteVersion = embeddedVersion;
                        }
                    } else {
                        ret = BundleStatus.upToDate;
                    }
                    messageManager.sendInfoNotification("remote.repository.finished.connection.to.remote");
                }
            } catch (IOException e) {
                messageManager.sendErrorNotification("remote.repository.cannot.read.installation.support.bundle",
                        serverConfiguration.getName(), e);
            } catch (OsgiClientException e) {
                messageManager.sendErrorNotification("remote.repository.osgi.client.problem",
                        serverConfiguration.getName(), e);
            }
        } else {
            messageManager.sendNotification("deploy.module.bundle.no.configuration.selected",
                    NotificationType.WARNING);
        }
        return ret;
    }

    public OsgiClient obtainSGiClient() {
        OsgiClient ret = null;

        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        if (serverConfiguration != null) {
            serverConfiguration.setServerStatus(ServerConfiguration.ServerStatus.connecting);
            try {
                boolean success = false;
                Result<ResourceProxy> result = null;
                Repository repository = obtainRepository(serverConfiguration, messageManager);
                if (repository != null) {
                    Command<ResourceProxy> command = repository.newListChildrenNodeCommand("/");
                    result = command.execute();
                    success = result.isSuccess();

                    messageManager.sendInfoNotification("remote.repository.connected.sling.repository", success);
                    if (success) {
                        serverConfiguration.setServerStatus(ServerConfiguration.ServerStatus.connected);
                        RepositoryInfo repositoryInfo = ServerUtil
                                .getRepositoryInfo(new IServer(serverConfiguration), new NullProgressMonitor());
                        ret = Activator.getDefault().getOsgiClientFactory().createOsgiClient(repositoryInfo);
                    }
                }
            } catch (URISyntaxException e) {
                messageManager.sendErrorNotification("remote.repository.uri.bad", serverConfiguration.getName(), e);
            }
        } else {
            messageManager.sendErrorNotification(
                    "server.configuration.cannot.connect.repository.missing.configuration",
                    serverConfiguration.getName());
        }
        return ret;
    }

    @Nullable
    public static Repository obtainRepository(@NotNull ServerConfiguration serverConfiguration,
            @NotNull MessageManager messageManager) {
        Repository ret = null;
        messageManager.sendInfoNotification("remote.repository.begin.connecting.sling.repository");
        try {
            ret = ServerUtil.connectRepository(new IServer(serverConfiguration), new NullProgressMonitor());
            // Check if the Connection is still alive by fetching the root nodes
            getChildrenNodes(ret, "/");
        } catch (CoreException e) {
            // Show Alert and exit
            //AS TODO: Seriously the RepositoryUtils class is throwing a IllegalArgumentException is it cannot connect to a Repo
            if (e.getCause().getClass() == IllegalArgumentException.class) {
                messageManager.showAlertWithArguments("server.configuration.cannot.connect.repository.refused",
                        serverConfiguration.getName());
            } else {
                messageManager.showAlertWithArguments("server.configuration.cannot.connect.repository",
                        serverConfiguration.getName(), e);
            }
        }
        return ret;
    }

    public static List<ResourceProxy> getChildrenNodes(Repository repository, String path) {
        List<ResourceProxy> ret = new ArrayList<ResourceProxy>();
        if (path != null && path.length() > 0) {
            if (path.charAt(0) != '/') {
                path = "/" + path;
            }
            try {
                Command<ResourceProxy> command = repository.newListChildrenNodeCommand(path);
                Result<ResourceProxy> result = command.execute();
                boolean success = result.isSuccess();
                if (success) {
                    ResourceProxy resourceProxy = result.get();
                    for (ResourceProxy childResourceProxy : resourceProxy.getChildren()) {
                        ret.add(childResourceProxy);
                    }
                } else {
                    result.get();
                }
            } catch (RepositoryException e) {
                //AS TODO: Throw Proper Exception
            }
        }
        return ret;
    }

    public void deployModules(final DataContext dataContext, boolean force, final ProgressHandler progressHandler) {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        if (serverConfiguration != null) {
            checkBinding(serverConfiguration, progressHandler);
            List<Module> moduleList = serverConfiguration.getModuleList();
            ProgressHandler progressHandlerSubTask = progressHandler.startSubTasks(moduleList.size(),
                    "Check Bindings");
            double i = 0;
            for (ServerConfiguration.Module module : moduleList) {
                progressHandlerSubTask.next("Deploy Module: " + module.getName());
                deployModule(dataContext, module, force, progressHandlerSubTask);
                i += 1;
            }
        } else {
            messageManager.sendNotification("deploy.modules.no.configuration.selected", NotificationType.WARNING);
        }
    }

    public void deployModule(@NotNull final DataContext dataContext, @NotNull ServerConfiguration.Module module,
            boolean force, final ProgressHandler progressHandler) {
        ProgressHandler progressHandlerSubTask = progressHandler.startSubTasks(2,
                "Bind Module: " + module.getName());
        messageManager.sendInfoNotification("remote.repository.begin.connecting.sling.repository");
        progressHandlerSubTask.next("Check Binding of Parent Module: " + module.getParent());
        checkBinding(module.getParent(), progressHandler);
        progressHandlerSubTask.next("Deploy Module to Server: " + module.getName());
        if (module.isPartOfBuild()) {
            if (module.isOSGiBundle()) {
                publishBundle(dataContext, module);
            } else if (module.isSlingPackage()) {
                //AS TODO: Add the synchronization of the entire module
                deploymentManager.publishModule(deploymentManager.new IntelliJModuleWrapper(module, myProject),
                        force);
            } else {
                messageManager.sendDebugNotification("debug.module.not.supported.package", module.getName());
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.unsupported);
            }
        } else {
            messageManager.sendDebugNotification("debug.module.not.part.of.build", module.getName());
            updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.excluded);
        }
    }

    public void connectInDebugMode(RunManagerEx runManager) {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        // Create Remote Connection to Server using the IntelliJ Run / Debug Connection
        //AS TODO: It is working but the configuration is listed and made persistent. That is not too bad because
        //AS TODO: after changes a reconnect will update the configuration.
        RemoteConfigurationType remoteConfigurationType = new RemoteConfigurationType();
        RunConfiguration runConfiguration = remoteConfigurationType.getFactory()
                .createTemplateConfiguration(myProject);
        RemoteConfiguration remoteConfiguration = (RemoteConfiguration) runConfiguration;
        // Server means if you are listening. If not you are attaching.
        remoteConfiguration.SERVER_MODE = false;
        remoteConfiguration.USE_SOCKET_TRANSPORT = true;
        remoteConfiguration.HOST = serverConfiguration.getHost();
        remoteConfiguration.PORT = serverConfiguration.getConnectionDebugPort() + "";
        // Set a Name of the Configuration so that it is properly listed.
        remoteConfiguration.setName(serverConfiguration.getName());
        RunnerAndConfigurationSettings configuration = new RunnerAndConfigurationSettingsImpl(
                (RunManagerImpl) runManager, runConfiguration, false);
        runManager.setTemporaryConfiguration(configuration);
        //AS TODO: Make sure that this is the proper way to obtain the DEBUG Executor
        Executor executor = ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
        ExecutionUtil.runConfiguration(configuration, executor);
        // Update the Modules with the Remote Sling Server
        OsgiClient osgiClient = obtainSGiClient();
        if (osgiClient != null) {
            BundleStatus status = checkAndUpdateSupportBundle(false);
            if (status != BundleStatus.failed) {
                checkModules(osgiClient);
            }
        }
    }

    public void stopDebugConnection(@NotNull DataContext dataContext) {
        ProcessHandler processHandler = getHandler(dataContext);
        if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) {
            ((KillableProcess) processHandler).killProcess();
            return;
        }

        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        if (serverConfiguration != null) {
            serverConfiguration.setServerStatus(ServerConfiguration.ServerStatus.disconnecting);
        }
        if (processHandler != null) {
            if (processHandler.detachIsDefault()) {
                processHandler.detachProcess();
            } else {
                processHandler.destroyProcess();
            }
        }
        if (serverConfiguration != null) {
            serverConfiguration.setServerStatus(ServerConfiguration.ServerStatus.disconnected);
        }
    }

    @Nullable
    private ProcessHandler getHandler(@NotNull DataContext dataContext) {
        final RunContentDescriptor contentDescriptor = LangDataKeys.RUN_CONTENT_DESCRIPTOR.getData(dataContext);
        if (contentDescriptor != null) {
            // toolwindow case
            return contentDescriptor.getProcessHandler();
        } else {
            // main menu toolbar
            final Project project = CommonDataKeys.PROJECT.getData(dataContext);
            final RunContentDescriptor selectedContent = project == null ? null
                    : ExecutionManager.getInstance(project).getContentManager().getSelectedContent();
            return selectedContent == null ? null : selectedContent.getProcessHandler();
        }
    }

    private void updateStatus(String configurationName,
            ServerConfiguration.SynchronizationStatus synchronizationStatus) {
        ServerConfiguration configuration = serverConfigurationManager
                .findServerConfigurationByName(configurationName);
        if (configuration != null) {
            configuration.setSynchronizationStatus(synchronizationStatus);
            serverConfigurationManager.updateServerConfiguration(configuration);
        }
    }

    public void updateServerStatus(String configurationName, ServerConfiguration.ServerStatus serverStatus) {
        ServerConfiguration configuration = serverConfigurationManager
                .findServerConfigurationByName(configurationName);
        if (configuration != null) {
            configuration.setServerStatus(serverStatus);
            serverConfigurationManager.updateServerConfiguration(configuration);
        }
    }

    public void updateStatus(ServerConfiguration configuration,
            ServerConfiguration.SynchronizationStatus synchronizationStatus) {
        if (configuration != null) {
            configuration.setSynchronizationStatus(synchronizationStatus);
            serverConfigurationManager.updateServerConfiguration(configuration);
        }
    }

    private void updateModuleStatus(Module module,
            ServerConfiguration.SynchronizationStatus synchronizationStatus) {
        if (module != null) {
            module.setStatus(synchronizationStatus);
            serverConfigurationManager.updateServerConfiguration(module.getParent());
        }
    }

    // Publishing Stuff --------------------------

    public void publishBundle(@NotNull final DataContext dataContext, final @NotNull Module module) {
        messageManager.sendInfoNotification("deploy.module.prepare", module);
        InputStream contents = null;
        // Check if this is a OSGi Bundle
        final UnifiedModule unifiedModule = module.getUnifiedModule();
        if (unifiedModule.isOSGiBundle()) {
            try {
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.updating);
                boolean localBuildDoneSuccessfully = true;
                //AS TODO: This should be isBuildLocally instead as we can now build both with Maven or Locally if Facet is specified
                if (module.getParent().isBuildWithMaven() && module.getUnifiedModule().isMavenBased()) {
                    localBuildDoneSuccessfully = false;
                    List<String> goals = MavenDataKeys.MAVEN_GOALS.getData(dataContext);
                    if (goals == null) {
                        goals = new ArrayList<String>();
                    }
                    if (goals.isEmpty()) {
                        //                        goals.add("package");
                        // If a module depends on anoher Maven module then we need to have it installed into the local repo
                        goals.add("install");
                    }
                    messageManager.sendInfoNotification("deploy.module.maven.goals", goals);
                    final MavenProjectsManager projectsManager = MavenActionUtil.getProjectsManager(dataContext);
                    if (projectsManager == null) {
                        messageManager.showAlert("Maven Failure",
                                "Could not find Maven Project Manager, need to build manually");
                    } else {
                        final ToolWindow tw = ToolWindowManager.getInstance(module.getProject())
                                .getToolWindow(ToolWindowId.RUN);
                        final boolean isShown = tw != null && tw.isVisible();
                        String workingDirectory = unifiedModule.getModuleDirectory();
                        MavenExplicitProfiles explicitProfiles = projectsManager.getExplicitProfiles();
                        final MavenRunnerParameters params = new MavenRunnerParameters(true, workingDirectory,
                                goals, explicitProfiles.getEnabledProfiles(),
                                explicitProfiles.getDisabledProfiles());
                        // This Monitor is used to know when the Maven build is done
                        RunExecutionMonitor rem = RunExecutionMonitor.getInstance(myProject);
                        // We need to tell the Monitor that we are going to start a Maven Build so that the Countdown Latch
                        // is ready
                        rem.startMavenBuild();
                        try {
                            ApplicationManager.getApplication().invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        MavenRunConfigurationType.runConfiguration(module.getProject(), params,
                                                null);
                                    } catch (RuntimeException e) {
                                        // Ignore it
                                        String message = e.getMessage();
                                    }
                                    if (isShown) {
                                        tw.hide(null);
                                    }
                                }
                            }, ModalityState.NON_MODAL);
                        } catch (IllegalStateException e) {
                            if (firstRun) {
                                firstRun = false;
                                messageManager.showAlert("deploy.module.maven.first.run.failure");
                            }
                        } catch (RuntimeException e) {
                            messageManager.sendDebugNotification("debug.maven.build.failed.unexpected", e);
                        }
                        // Now we can wait for the process to end
                        switch (RunExecutionMonitor.getInstance(myProject).waitFor()) {
                        case done:
                            messageManager.sendInfoNotification("deploy.module.maven.done");
                            localBuildDoneSuccessfully = true;
                            break;
                        case timedOut:
                            messageManager.sendInfoNotification("deploy.module.maven.timedout");
                            messageManager.showAlert("deploy.module.maven.timedout");
                            break;
                        case interrupted:
                            messageManager.sendInfoNotification("deploy.module.maven.interrupted");
                            messageManager.showAlert("deploy.module.maven.interrupted");
                            break;
                        }
                    }
                } else if (!module.getUnifiedModule().isMavenBased()) {
                    // Compile the IntelliJ way
                    final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
                    final CompileScope moduleScope = compilerManager
                            .createModuleCompileScope(module.getUnifiedModule().getModule(), true);
                    WaitableRunner<AtomicBoolean> runner = new WaitableRunner<AtomicBoolean>(
                            new AtomicBoolean(false)) {
                        @Override
                        public void run() {
                            compilerManager.make(moduleScope, new CompileStatusNotification() {
                                public void finished(boolean aborted, int errors, int warnings,
                                        CompileContext compileContext) {
                                    getResponse().set(!aborted && errors == 0);
                                }
                            });
                        }
                    };
                    runAndWait(runner);
                    //                    final CountDownLatch waiter = new CountDownLatch(1);
                    //                    final AtomicBoolean checker = new AtomicBoolean(false);
                    //                    ApplicationManager.getApplication().invokeLater(
                    //                        new Runnable() {
                    //                            public void run() {
                    //                            }
                    //                        }
                    //                    );
                    //                    try {
                    //                        waiter.await();
                    //                    } catch(InterruptedException e) {
                    //                        //AS TODO: Show Info Notification and Alert
                    //                    }
                    localBuildDoneSuccessfully = runner.getResponse().get();
                }
                if (localBuildDoneSuccessfully) {
                    File buildDirectory = new File(module.getUnifiedModule().getBuildDirectoryPath());
                    if (buildDirectory.exists() && buildDirectory.isDirectory()) {
                        File buildFile = new File(buildDirectory, module.getUnifiedModule().getBuildFileName());
                        messageManager.sendDebugNotification("debug.build.file.name", buildFile.toURL());
                        if (buildFile.exists()) {
                            //AS TODO: This looks like OSGi Symbolic Name to be used here
                            EmbeddedArtifact bundle = new EmbeddedArtifact(module.getSymbolicName(),
                                    module.getVersion(), buildFile.toURL());
                            contents = bundle.openInputStream();
                            obtainSGiClient().installBundle(contents, bundle.getName());
                            module.setStatus(ServerConfiguration.SynchronizationStatus.upToDate);
                        } else {
                            messageManager.showAlertWithArguments("deploy.module.maven.missing.build.file",
                                    buildFile.getAbsolutePath());
                        }
                    }
                    updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.upToDate);
                    messageManager.sendInfoNotification("deploy.module.success", module);
                }
            } catch (MalformedURLException e) {
                module.setStatus(ServerConfiguration.SynchronizationStatus.failed);
                messageManager.sendErrorNotification("deploy.module.failed.bad.url", e);
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
            } catch (OsgiClientException e) {
                module.setStatus(ServerConfiguration.SynchronizationStatus.failed);
                messageManager.sendErrorNotification("deploy.module.failed.client", e);
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
            } catch (IOException e) {
                module.setStatus(ServerConfiguration.SynchronizationStatus.failed);
                messageManager.sendErrorNotification("deploy.module.failed.io", e);
                updateModuleStatus(module, ServerConfiguration.SynchronizationStatus.failed);
            } finally {
                IOUtils.closeQuietly(contents);
            }
        } else {
            messageManager.sendNotification("deploy.module.unsupported.maven.packaging", NotificationType.WARNING);
        }
    }

    public enum FileChangeType {
        CHANGED, CREATED, DELETED, MOVED, COPIED
    };

    public long getLastModificationTimestamp(Module module) {
        long ret = -1;

        List<String> resourceList = findContentResources(module);
        Set<String> allResourcesUpdatedList = new HashSet<String>();
        UnifiedModule unifiedModule = module.getUnifiedModule();
        VirtualFile baseFile = module.getProject().getBaseDir();
        for (String resource : resourceList) {
            VirtualFile resourceFile = baseFile.getFileSystem().findFileByPath(resource);
            messageManager.sendDebugNotification("debug.last.modification.time.resource.file", resourceFile);
            List<VirtualFile> changedResources = new ArrayList<VirtualFile>();
            getChangedResourceList(resourceFile, changedResources);
            //AS TODO: Create a List of Changed Resources
            for (VirtualFile changedResource : changedResources) {
                long fileTimestamp = Util.getModificationStamp(changedResource);
                if (fileTimestamp > 0) {
                    ret = Math.max(ret, fileTimestamp);
                    long parentLastModificationTimestamp = getParentLastModificationTimestamp(module,
                            resourceFile.getPath(), changedResource, allResourcesUpdatedList);
                    ret = Math.max(ret, parentLastModificationTimestamp);
                }
            }
        }

        return ret;
    }

    private long getParentLastModificationTimestamp(Module module, String basePath, VirtualFile file,
            Set<String> handledPaths) {
        long ret = -1;
        VirtualFile parentFile = file.getParent();
        messageManager.sendDebugNotification("debug.last.modification.time.parent.resource.file", parentFile);
        if (parentFile.getPath().equals(basePath)) {
            return ret;
        }
        // already published by us, a parent of another resource that was published in this execution
        if (handledPaths.contains(parentFile.getPath())) {
            return ret;
        }
        long parentLastModificationTimestamp = getParentLastModificationTimestamp(module, basePath, parentFile,
                handledPaths);
        ret = Math.max(parentLastModificationTimestamp, ret);
        long timestamp = file.getTimeStamp();
        long fileTimestamp = Util.getModificationStamp(file);
        if (fileTimestamp > 0) {
            ret = Math.max(timestamp, ret);
        }
        return ret;
    }

    /**
     * Builds up the list of resource files recursively of the given resource
     *
     * @param resource The resource we start with. If it is a directory we call this method with that directory
     * @param resourceList The list we add all resource files. At the end this list contains all files inside
     *                     the original resource.
     */
    private void getChangedResourceList(VirtualFile resource, List<VirtualFile> resourceList) {
        if (resource.isDirectory()) {
            List<VirtualFile> children = Arrays.asList(resource.getChildren());
            for (VirtualFile child : children) {
                getChangedResourceList(child, resourceList);
            }
        } else {
            resourceList.add(resource);
        }
    }

    public void handleFileChanges(List<FileChange> fileChangeList) {
        Map<String, Module> resourcePathToModuleMap = new HashMap<String, Module>();
        for (FileChange fileChange : fileChangeList) {
            String filePath = fileChange.getFile().getPath();
            // First go over the map to see if we already found the module
            boolean found = false;
            for (Map.Entry<String, Module> entry : resourcePathToModuleMap.entrySet()) {
                Module module = entry.getValue();
                String path = entry.getKey();
                if (filePath.startsWith(path)) {
                    fileChange.setModule(module);
                    fileChange.setResourcePath(path);
                    found = true;
                    break;
                }
            }
            if (!found) {
                // If not found we loop over the modules and see if one is
                List<Module> moduleList = selectionHandler.getModuleDescriptorListOfCurrentConfiguration();
                for (Module module : moduleList) {
                    if (module.isSlingPackage()) {
                        String contentPath = findContentResource(module, filePath);
                        if (contentPath != null) {
                            // This file belongs to this module so we are good to publish it
                            fileChange.setModule(module);
                            fileChange.setResourcePath(contentPath);
                            resourcePathToModuleMap.put(contentPath, module);
                            break;
                        }
                    } else if (module.isOSGiBundle()) {
                        // Here we are not interested in a source file but rather in the Artifact. If it is the artifact then
                        // we mark the module as outdated
                        UnifiedModule unifiedModule = module.getUnifiedModule();
                        if (filePath.startsWith(unifiedModule.getBuildDirectoryPath())) {
                            // Check if it is the build file
                            String fileName = fileChange.getFile().getName();
                            String buildFileName = unifiedModule.getBuildFileName();
                            if (fileName.equals(buildFileName)) {
                                messageManager.sendInfoNotification("server.update.file.change.prepare", filePath,
                                        fileChange.getFileChangeType());
                                module.setStatus(ServerConfiguration.SynchronizationStatus.outdated);
                            }
                        }
                    }
                }
            }
        }
        if (!resourcePathToModuleMap.isEmpty()) {
            Module module = resourcePathToModuleMap.values().iterator().next();
            Repository repository = ServerUtil.getConnectedRepository(new IServer(module.getParent()),
                    new NullProgressMonitor(), messageManager);
            if (repository != null) {
                for (FileChange fileChange : fileChangeList) {
                    try {
                        VirtualFile file = fileChange.getFile();
                        String path = file.getPath();
                        FileChangeType type = fileChange.getFileChangeType();
                        String basePath = fileChange.getResourcePath();
                        Module currentModule = fileChange.getModule();
                        messageManager.sendInfoNotification("server.update.file.change.prepare", path, type);
                        messageManager.sendDebugNotification("debug.obtained.repository", repository);
                        Command<?> command = null;
                        switch (type) {
                        case CHANGED:
                        case CREATED:
                            command = deploymentManager.addFileCommand(repository,
                                    deploymentManager.new IntelliJModuleWrapper(currentModule, myProject),
                                    deploymentManager.new IntelliJFileWrapper(file), false);
                            break;
                        case DELETED:
                            command = deploymentManager.removeFileCommand(repository,
                                    deploymentManager.new IntelliJModuleWrapper(currentModule, myProject),
                                    deploymentManager.new IntelliJFileWrapper(file));
                            break;
                        }
                        messageManager.sendDebugNotification("debug.resource.command", command);
                        if (command != null) {
                            Set<String> handledPaths = new HashSet<String>();
                            deploymentManager.ensureParentIsPublished(
                                    deploymentManager.new IntelliJModuleWrapper(currentModule, myProject),
                                    //AS Make sure the basepath is in forward slash notation
                                    basePath.replace("\\", "/"), deploymentManager.new IntelliJFileWrapper(file),
                                    repository, handledPaths, true);
                            deploymentManager.execute(command);
                            // Add a property that can be used later to avoid a re-sync if not needed
                            Util.setModificationStamp(file);
                            messageManager.sendInfoNotification("server.update.file.change.success", path);
                        } else {
                            messageManager.sendInfoNotification("server.update.file.change.failed", path,
                                    currentModule);
                        }
                    } catch (ConnectorException e) {
                        e.printStackTrace();
                    } catch (SerializationException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public String findContentResource(Module module, String filePath) {
        List<String> resourceList = findContentResources(module, filePath);
        return resourceList.isEmpty() ? null : resourceList.get(0);
    }

    public List<String> findContentResources(Module module) {
        return findContentResources(module, null);
    }

    public List<String> findContentResources(Module module, String filePath) {
        List<String> ret = new ArrayList<String>();
        UnifiedModule unifiedModule = module.getUnifiedModule();
        List<String> contentDirectoryPaths = unifiedModule.getContentDirectoryPaths();
        for (String basePath : contentDirectoryPaths) {
            messageManager.sendDebugNotification("debug.content.base.path", basePath);
            //AS TODO: Paths from Windows have backlashes instead of forward slashes
            //AS TODO: It is possible that certain files are in forward slashes even on Windows
            String myFilePath = filePath == null ? null : filePath.replace("\\", "/");
            String myBasePath = basePath == null ? null : basePath.replace("\\", "/");
            if (Util.pathEndsWithFolder(basePath, JCR_ROOT_FOLDER_NAME)
                    && (myFilePath == null || myFilePath.startsWith(myBasePath))) {
                ret.add(basePath);
                break;
            }
        }
        return ret;
    }

    /**
     * Checks if the current selected and connected Server Connection are the same (to avoid accidental deployments)
     * and also checks if automatic builds are supported
     * @param showAlert Displays an alert if the current selected and connected server connection are different
     * @param automaticBuild If true it will check if the publish type is set to automatically on change
     * @return True if the connection are in sync and if automatic build are support (if it is set to be checked)
     */
    public boolean checkSelectedServerConfiguration(boolean showAlert, boolean automaticBuild) {
        ServerConfiguration serverConfiguration = selectionHandler.getCurrentConfiguration();
        ServerConfiguration connectedServerConfiguration = serverConfigurationManager
                .findConnectedServerConfiguration();
        if (connectedServerConfiguration != null && serverConfiguration != connectedServerConfiguration) {
            if (showAlert) {
                messageManager.showAlert("remote.repository.selection.mismatch");
            }
            return false;
        } else if (serverConfiguration != null && automaticBuild
                && serverConfiguration.getPublishType() != ServerConfiguration.PublishType.automaticallyOnChange) {
            return false;
        } else {
            return true;
        }
    }
}