getRegistryModuleIDs()
    {
        // Get the IDs of all modules from the registry
        RegistryKey moduleKeyAllIds = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, null);
        
        return registryService.getChildElements(moduleKeyAllIds);
    }
    
    /**
     * Returns the version number of a module from the Registry.
     * 
     * @param moduleId String
     * @return ModuleVersionNumber
     */
    ModuleVersionNumber getVersion(String moduleId)
    {
        RegistryKey moduleKeyCurrentVersion = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION);
        Serializable versionCurrent = registryService.getProperty(moduleKeyCurrentVersion);
        return getModuleVersionNumber(versionCurrent);
    }
    
    /**
     * Checks to see if there are any modules registered as installed that aren't in the
     * list of modules taken from the WAR.
     * 
     * Currently, the behaviour specified is that a warning is generated only.
     */
    private void checkForMissingModules()
    {
        // Get the IDs of all modules from the registry
        Collection moduleIds = getRegistryModuleIDs();
        
        // Check that each module is present in the distribution
        for (String moduleId : moduleIds)
        {
            ModuleDetails moduleDetails = moduleService.getModule(moduleId);
            if (moduleDetails != null)
            {
                if (logger.isDebugEnabled())
                {
                    logger.debug("Installed module found in distribution: " + moduleId);
                }
            }
            else
            {
                // Get the specifics of the missing module
                
                ModuleVersionNumber versionCurrent = getVersion(moduleId);
                // The module is missing, so warn
                loggerService.warn(I18NUtil.getMessage(MSG_MISSING, moduleId, versionCurrent));
            }
        }
    }
    
    /**
     * Copies, where necessary, the module registry details from the alias details
     * and removes the alias details.
     */
    private void renameModule(ModuleDetails module)
    {
        String moduleId = module.getId();
        List moduleAliases = module.getAliases();
        
        // Get the IDs of all modules from the registry
        RegistryKey moduleKeyAllIds = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, null);
        Collection registeredModuleIds = registryService.getChildElements(moduleKeyAllIds);
        
        // Firstly, is the module installed?
        if (registeredModuleIds.contains(moduleId))
        {
            // It is there, so we do nothing
            return;
        }
        // Check if any of the registered modules are on the alias list
        for (String moduleAlias : moduleAliases)
        {
            // Is this alias registered?
            if (!registeredModuleIds.contains(moduleAlias))
            {
                // No alias registered
                continue;
            }
            // We found an alias and have to rename it to the new module ID
            RegistryKey moduleKeyNew = new RegistryKey(
                    ModuleComponentHelper.URI_MODULES_1_0,
                    REGISTRY_PATH_MODULES, moduleId, null);
            RegistryKey moduleKeyOld = new RegistryKey(
                    ModuleComponentHelper.URI_MODULES_1_0,
                    REGISTRY_PATH_MODULES, moduleAlias, null);
            // Copy it all
            registryService.copy(moduleKeyOld, moduleKeyNew);
            // Remove the source
            registryService.delete(moduleKeyOld);
            // Done
            if (logger.isDebugEnabled())
            {
                logger.debug("Moved old module alias to new module ID: \n" +
                        "   Alias:  " + moduleAlias + "\n" +
                        "   Module: " + moduleId);
            }
            break;
        }
    }
    
    /**
     * Does the actual work without fussing about transactions and authentication.
     * Module dependencies will be started first, but a module will only be started
     * once.
     * 
     * @param module                the module to start
     * @param startedModules        the IDs of modules that have already started
     * @param executedComponents    keep track of the executed components
     */
    private void startModule(ModuleDetails module, Set startedModules, Set executedComponents)
    {
        String moduleId = module.getId();
        ModuleVersionNumber moduleNewVersion = module.getModuleVersionNumber();
        
        // Double check whether we have done this module already
        if (startedModules.contains(moduleId))
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("Module '" + module + "' already started");
            }
            return;
        }
        
        // Start dependencies
        List moduleDependencies = module.getDependencies();
        for (ModuleDependency moduleDependency : moduleDependencies)
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("Module '" + module + "' depends on: " + moduleDependency);
            }
            // Get the dependency
            String moduleDependencyId = moduleDependency.getDependencyId();
            ModuleDetails moduleDependencyDetails = moduleService.getModule(moduleDependencyId);
            // Check that it is there
            if (moduleDependencyDetails == null)
            {
                // The dependency is not there
                // List required dependencies
                StringBuilder sb = new StringBuilder(128);
                for (ModuleDependency dependency : moduleDependencies)
                {
                    sb.append("\n").append(dependency);
                }
                String msg = I18NUtil.getMessage(
                        MSG_DEPENDENCIES,
                        moduleId, moduleNewVersion, sb.toString());
                logger.info(msg);
                // Now fail
                throw AlfrescoRuntimeException.create(
                        ERR_MISSING_DEPENDENCY,
                        moduleId, moduleNewVersion, moduleDependency);
            }
            // The dependency is installed, so start it
            startModule(moduleDependencyDetails, startedModules, executedComponents);
        }
        
        // Check if the module needs a rename first
        renameModule(module);
        
        // First check that the module version is fundamentally compatible with the repository
        VersionNumber repoVersionNumber = descriptorService.getServerDescriptor().getVersionNumber();
        VersionNumber minRepoVersionNumber = module.getRepoVersionMin();
        VersionNumber maxRepoVersionNumber = module.getRepoVersionMax();
        if ((minRepoVersionNumber != null && repoVersionNumber.compareTo(minRepoVersionNumber) < 0) ||
            (maxRepoVersionNumber != null && repoVersionNumber.compareTo(maxRepoVersionNumber) > 0))
        {
            // The current repo version is not supported
            throw AlfrescoRuntimeException.create(
                    ERR_UNSUPPORTED_REPO_VERSION,
                    moduleId, moduleNewVersion, repoVersionNumber, minRepoVersionNumber, maxRepoVersionNumber);
        }
        
        // Get the module details from the registry
        RegistryKey moduleKeyInstalledVersion = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_INSTALLED_VERSION);
        RegistryKey moduleKeyCurrentVersion = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION);
        Serializable moduleInstallVersion = registryService.getProperty(moduleKeyInstalledVersion);
        Serializable moduleCurrentVersion = registryService.getProperty(moduleKeyCurrentVersion);
        String msg = null;
        if (moduleCurrentVersion == null)                                 // No previous record of it
        {
            msg = I18NUtil.getMessage(MSG_INSTALLING, moduleId, moduleNewVersion);
            // Record the install version
            registryService.addProperty(moduleKeyInstalledVersion, moduleNewVersion);
            moduleInstallVersion = moduleNewVersion;
            moduleCurrentVersion = moduleNewVersion;
        }
        else                                    // It is an upgrade or is the same
        {
            ModuleVersionNumber currentModuleVersion = getModuleVersionNumber(moduleCurrentVersion);
            
            // Check that we have an installed version
            if (moduleInstallVersion == null)
            {
                // A current version, but no installed version
                logger.warn(I18NUtil.getMessage(WARN_NO_INSTALL_VERSION, moduleId, moduleCurrentVersion));
                // Record the install version
                registryService.addProperty(moduleKeyInstalledVersion, currentModuleVersion);
                moduleInstallVersion = moduleCurrentVersion;
            }
            
            if (currentModuleVersion.compareTo(moduleNewVersion) == 0)       // The current version is the same
            {
                msg = I18NUtil.getMessage(MSG_STARTING, moduleId, moduleNewVersion);
            }
            else if (currentModuleVersion.compareTo(moduleNewVersion) > 0)   // Downgrading not supported
            {
                throw AlfrescoRuntimeException.create(ERR_NO_DOWNGRADE, moduleId, moduleCurrentVersion, moduleNewVersion);
            }
            else                                                    // This is an upgrade
            {
                msg = I18NUtil.getMessage(MSG_UPGRADING, moduleId, moduleNewVersion, moduleCurrentVersion);
            }
        }
        loggerService.info(msg);
        // Record the current version
        registryService.addProperty(moduleKeyCurrentVersion, moduleNewVersion);
        
        Map componentsByName = getComponents(moduleId);
        for (ModuleComponent component : componentsByName.values())
        {
            executeComponent(moduleId, moduleNewVersion, component, executedComponents);
        }
        
        // Keep track of the ID as it started successfully
        startedModules.add(moduleId);
        
        // Done
        if (logger.isDebugEnabled())
        {
            logger.debug("Started module '" + module + "' including " + executedComponents.size() + "components.");
        }
    }
    
    protected ModuleVersionNumber getModuleVersionNumber(Serializable moduleVersion)
    {
        if (moduleVersion instanceof ModuleVersionNumber) return (ModuleVersionNumber) moduleVersion;
        if (moduleVersion instanceof VersionNumber) return new ModuleVersionNumber((VersionNumber)moduleVersion);
        if (moduleVersion instanceof String) return new ModuleVersionNumber((String)moduleVersion);
        throw new ModuleManagementToolException("Invalid moduleVersion");
    }
    /**
     * Execute the component, respecting dependencies.
     */
    private void executeComponent(
            String moduleId,
            ModuleVersionNumber currentVersion,
            ModuleComponent component,
            Set executedComponents)
    {
        // Ignore if it has been executed in this run already
        if (executedComponents.contains(component))
        {
            // Already done
            if (logger.isDebugEnabled())
            {
                logger.debug("Skipping component already executed in this run: \n" +
                        "   Component:      " + component);
            }
            return;
        }
        // Keep track of the fact that we considered it for execution
        executedComponents.add(component);
        
        // Check the version applicability
        ModuleVersionNumber minVersion = component.getAppliesFromVersionNumber();
        ModuleVersionNumber maxVersion = component.getAppliesToVersionNumber();
        if (currentVersion.compareTo(minVersion) < 0 || currentVersion.compareTo(maxVersion) > 0)
        {
            // It is out of the allowable range for execution so we just ignore it
            if (logger.isDebugEnabled())
            {
                logger.debug("Skipping component that doesn't apply to the module installation version: \n" +
                        "   Component:       " + component + "\n" +
                        "   Module:          " + moduleId + "\n" +
                        "   Current Version: " + currentVersion + "\n" +
                        "   Applies From :   " + minVersion + "\n" +
                        "   Applies To   :   " + maxVersion);
            }
            return;
        }
        
        // Construct the registry key to store the execution date
        String name = component.getName();
        RegistryKey executionDateKey = new RegistryKey(
                ModuleComponentHelper.URI_MODULES_1_0,
                REGISTRY_PATH_MODULES, moduleId, REGISTRY_PATH_COMPONENTS, name, REGISTRY_PROPERTY_EXECUTION_DATE);
        
        // Check if the component has been executed
        Date executionDate = (Date) registryService.getProperty(executionDateKey);
        if (executionDate != null && component.isExecuteOnceOnly())
        {
            // It has been executed and is scheduled for a single execution - leave it
            if (logger.isDebugEnabled())
            {
                logger.debug("Skipping already-executed module component: \n" +
                        "   Component:      " + component + "\n" +
                        "   Execution Time: " + executionDate);
            }
            return;
        }
        // It may have been executed, but not in this run and it is allowed to be repeated
        // Check for dependencies
        List dependencies = component.getDependsOn();
        for (ModuleComponent dependency : dependencies)
        {
            executeComponent(moduleId, currentVersion, dependency, executedComponents);
        }
        // Execute the component itself
        component.execute();
        // Keep track of it in the registry
        registryService.addProperty(executionDateKey, new Date());
        // Done
        if (logger.isDebugEnabled())
        {
            logger.debug("Executed module component: \n" +
                    "   Component:      " + component);
        }
    }
}