diff --git a/config/alfresco/messages/module-messages.properties b/config/alfresco/messages/module-messages.properties index 6b6fd6a804..7c857aa8b5 100644 --- a/config/alfresco/messages/module-messages.properties +++ b/config/alfresco/messages/module-messages.properties @@ -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. \ No newline at end of file +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. diff --git a/source/java/org/alfresco/repo/module/LoggerModuleComponent.java b/source/java/org/alfresco/repo/module/LoggerModuleComponent.java new file mode 100644 index 0000000000..dfccc7599b --- /dev/null +++ b/source/java/org/alfresco/repo/module/LoggerModuleComponent.java @@ -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: + *
+ * log4j.logger.org.alfresco.modules.MyModule.DumpMessageComponent=INFO
+ * 
+ * + * @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; + } + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java index d95e6af4e5..4571286175 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -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 executedComponents = new HashSet(10); + final Set startedModules = new HashSet(2); for (final ModuleDetails module : modules) { TransactionWork startModuleWork = new TransactionWork() { 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 executedComponents) + { + Set missedComponents = new HashSet(executedComponents); + + // Iterate over each module registered by components + for (Map.Entry> entry : componentsByNameByModule.entrySet()) + { + String moduleId = entry.getKey(); + Map componentsByName = entry.getValue(); + // Iterate over each component registered against the module ID + for (Map.Entry 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 executedComponents) + private void startModule(ModuleDetails module, Set startedModules, Set 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 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()) diff --git a/source/test-resources/module/test_v1.amp b/source/test-resources/module/test_v1.amp index a3bf51a6bb..9480ea6978 100644 Binary files a/source/test-resources/module/test_v1.amp and b/source/test-resources/module/test_v1.amp differ diff --git a/source/test-resources/module/test_v2.amp b/source/test-resources/module/test_v2.amp index ea9a5fcbb9..2872ca1238 100644 Binary files a/source/test-resources/module/test_v2.amp and b/source/test-resources/module/test_v2.amp differ