Repository startup support for module dependencies.

Modules are started in in order of their dependencies.
Components that don't execute due to incorrect registration are detected.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5606 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-05-03 14:10:11 +00:00
parent 7ccadfe5da
commit 38c2a47e26
5 changed files with 220 additions and 13 deletions

View File

@@ -4,13 +4,17 @@ module.msg.found_modules=Found {0} module(s).
module.msg.starting= Starting module ''{0}'' version {1}.
module.msg.installing= Installing module ''{0}'' version {1}.
module.msg.upgrading= Upgrading module ''{0}'' version {1} (was {2}).
module.msg.missing= A previously-installed module ''{0}'' (version {1}) is not present in your distribution.
module.msg.missing= A previously-installed module ''{0}'' (version {1}) is not present in your distribution.
module.msg.dependencies= Module ''{0}'' version {1} has the following dependencies: {1}
module.warn.no_install_version=Module ''{0}'' had no install version. Assuming version {1} was installed.
module.err.missing_dependency=\nModule ''{0}'' version {1} depends on module ''{2}'', which has not been installed.
module.err.downgrading_not_supported=\nDowngrading of modules is not supported.\nModule ''{0}'' version {1} is currently installed and must be uninstalled before version {2} can be installed.
module.err.unsupported_repo_version=\nModule ''{0}'' version {1} is incompatible with the current repository version {2}.\n The repository version required must be in range [{3} : {4}].
module.err.already_executed=The module component has already been executed: {0}.{1}
module.err.execution_failed=A module component ''{0}'' failed to execute: {1}
module.err.component_already_registered=A component named ''{0}'' has already been registered for module ''{1}''.
module.err.unable_to_open_module_properties=The module properties file ''{0}'' could not be read.
module.err.unable_to_open_module_properties=The module properties file ''{0}'' could not be read.
module.err.component_in_missing_module=The component ''{0}'' belongs to a non-existent module ''{1}''.
module.err.orphaned_components={0} module components were not considered for execution.

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.module;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Module component that logs a message on startup. The log category
* used will be the ID of the module that contains the component and the
* name of the component itself. For example:
* <pre>
* log4j.logger.org.alfresco.modules.MyModule.DumpMessageComponent=INFO
* </pre>
*
* @author Derek Hulley
* @since 2.1
*/
public class LoggerModuleComponent extends AbstractModuleComponent
{
private enum LogLevel
{
INFO, WARN, ERROR;
}
private LogLevel logLevel;
private String message;
public LoggerModuleComponent()
{
logLevel = LogLevel.INFO;
}
/**
* Set the level at which the bean must log the message.
* @param logLevel One of the {@link LogLevel values}.
* The default is {@link LogLevel#INFO}.
*/
public void setLogLevel(String logLevel)
{
this.logLevel = LogLevel.valueOf(logLevel);
}
/**
* Set the message that must be logged. This can be a message string
* or an ID of an internationalized string.
*
* @param message a message to log at the {@link #setLogLevel(String) log level}
*/
public void setMessage(String message)
{
this.message = message;
}
@Override
protected void checkProperties()
{
PropertyCheck.mandatory(this, "message", message);
// fulfil contract of override
super.checkProperties();
}
@Override
protected void executeInternal() throws Throwable
{
String moduleId = super.getModuleId();
String name = super.getName();
Log logger = LogFactory.getLog(moduleId + "." + name);
switch (logLevel)
{
case INFO:
logger.info(message);
break;
case WARN:
logger.warn(message);
break;
case ERROR:
logger.error(message);
break;
}
}
}

View File

@@ -43,6 +43,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.TransactionUtil;
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.module.ModuleDependency;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleService;
import org.alfresco.service.descriptor.DescriptorService;
@@ -71,11 +72,15 @@ public class ModuleComponentHelper
private static final String MSG_STARTING = "module.msg.starting";
private static final String MSG_INSTALLING = "module.msg.installing";
private static final String MSG_UPGRADING = "module.msg.upgrading";
private static final String MSG_DEPENDENCIES = "module.msg.dependencies";
private static final String MSG_MISSING = "module.msg.missing";
private static final String WARN_NO_INSTALL_VERSION = "module.warn.no_install_version";
private static final String ERR_MISSING_DEPENDENCY = "module.err.missing_dependency";
private static final String ERR_UNSUPPORTED_REPO_VERSION = "module.err.unsupported_repo_version";
private static final String ERR_NO_DOWNGRADE = "module.err.downgrading_not_supported";
private static final String ERR_COMPONENT_ALREADY_REGISTERED = "module.err.component_already_registered";
private static final String MSG_MISSING = "module.msg.missing";
private static final String ERR_COMPONENT_IN_MISSING_MODULE = "module.err.component_in_missing_module";
private static final String ERR_ORPHANED_COMPONENTS = "module.err.orphaned_components";
private static Log logger = LogFactory.getLog(ModuleComponentHelper.class);
private static Log loggerService = LogFactory.getLog(ModuleServiceImpl.class);
@@ -207,26 +212,25 @@ public class ModuleComponentHelper
loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size()));
// Process each module in turn. Ordering is not important.
final Set<ModuleComponent> executedComponents = new HashSet<ModuleComponent>(10);
final Set<String> startedModules = new HashSet<String>(2);
for (final ModuleDetails module : modules)
{
TransactionWork<Object> startModuleWork = new TransactionWork<Object>()
{
public Object doWork() throws Exception
{
startModule(module, executedComponents);
startModule(module, startedModules, executedComponents);
return null;
}
};
TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, startModuleWork);
}
// We have finished executing any components
if (logger.isDebugEnabled())
{
logger.debug("Executed " + executedComponents.size() + " components");
}
// Check for missing modules.
checkForMissingModules();
// Check that all components where executed, or considered for execution
checkForOrphanComponents(executedComponents);
// Restore the original authentication
authenticationComponent.setCurrentAuthentication(authentication);
@@ -237,6 +241,46 @@ public class ModuleComponentHelper
}
}
/**
* Checks that all components have been executed or considered for execution.
* @param executedComponents
*/
private void checkForOrphanComponents(Set<ModuleComponent> executedComponents)
{
Set<ModuleComponent> missedComponents = new HashSet<ModuleComponent>(executedComponents);
// Iterate over each module registered by components
for (Map.Entry<String, Map<String, ModuleComponent>> entry : componentsByNameByModule.entrySet())
{
String moduleId = entry.getKey();
Map<String, ModuleComponent> componentsByName = entry.getValue();
// Iterate over each component registered against the module ID
for (Map.Entry<String, ModuleComponent> entryInner : componentsByName.entrySet())
{
String componentName = entryInner.getKey();
ModuleComponent component = entryInner.getValue();
// Check if it has been executed
if (executedComponents.contains(component))
{
// It was executed, so remove it from the missed components set
missedComponents.remove(component);
}
else
{
String msg = I18NUtil.getMessage(
ERR_COMPONENT_IN_MISSING_MODULE,
componentName, moduleId);
logger.error(msg);
}
}
}
// Dump if there were orphans
if (missedComponents.size() > 0)
{
throw AlfrescoRuntimeException.create(ERR_ORPHANED_COMPONENTS, missedComponents.size());
}
}
/**
* Checks to see if there are any modules registered as installed that aren't in the
* list of modules taken from the WAR.
@@ -329,12 +373,62 @@ public class ModuleComponentHelper
/**
* 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<ModuleComponent> executedComponents)
private void startModule(ModuleDetails module, Set<String> startedModules, Set<ModuleComponent> executedComponents)
{
String moduleId = module.getId();
VersionNumber moduleNewVersion = module.getVersion();
// 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<ModuleDependency> 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);
@@ -404,10 +498,13 @@ public class ModuleComponentHelper
executeComponent(moduleId, moduleInstallVersion, component, executedComponents);
}
// Keep track of the ID as it started successfully
startedModules.add(moduleId);
// Done
if (logger.isDebugEnabled())
{
logger.debug("Started module: " + module);
logger.debug("Started module '" + module + "' including " + executedComponents.size() + "components.");
}
}
@@ -431,6 +528,8 @@ public class ModuleComponentHelper
}
return;
}
// Keep track of the fact that we considered it for execution
executedComponents.add(component);
// Check the version applicability
VersionNumber minVersion = component.getAppliesFromVersionNumber();
@@ -478,8 +577,7 @@ public class ModuleComponentHelper
}
// Execute the component itself
component.execute();
// Keep track of it in the registry and in this run
executedComponents.add(component);
// Keep track of it in the registry
registryService.addProperty(executionDateKey, new Date());
// Done
if (logger.isDebugEnabled())