/* * Copyright (C) 2007 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * 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 org.alfresco.repo.module; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.acegisecurity.Authentication; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.admin.registry.RegistryKey; import org.alfresco.repo.admin.registry.RegistryService; 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.ModuleDetails; import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VersionNumber; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Helper class to split up some of the code for managing module components. This class handles * the execution of the module components. * * @author Derek Hulley */ public class ModuleComponentHelper { public static final String URI_MODULES_1_0 = "http://www.alfresco.org/system/modules/1.0"; private static final String REGISTRY_PATH_MODULES = "modules"; private static final String REGISTRY_PROPERTY_INSTALLED_VERSION = "installedVersion"; private static final String REGISTRY_PROPERTY_CURRENT_VERSION = "currentVersion"; private static final String REGISTRY_PATH_COMPONENTS = "components"; private static final String REGISTRY_PROPERTY_EXECUTION_DATE = "executionDate"; private static final String MSG_FOUND_MODULES = "module.msg.found_modules"; 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 ERR_NO_DOWNGRADE = "module.err.downgrading_not_supported"; private static final String ERR_COMPONENT_ALREADY_REGISTERED = "module.err.component_already_registered"; private static Log logger = LogFactory.getLog(ModuleComponentHelper.class); private static Log loggerService = LogFactory.getLog(ModuleServiceImpl.class); private ServiceRegistry serviceRegistry; private AuthenticationComponent authenticationComponent; private RegistryService registryService; private ModuleService moduleService; private Map> componentsByNameByModule; /** Default constructor */ public ModuleComponentHelper() { componentsByNameByModule = new HashMap>(7); } /** * @param serviceRegistry provides access to the service APIs */ public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } /** * @param authenticationComponent allows execution as system user. */ public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) { this.authenticationComponent = authenticationComponent; } /** * @param registryService the service used to persist component execution details. */ public void setRegistryService(RegistryService registryService) { this.registryService = registryService; } /** * @param moduleService the service from which to get the available modules. */ public void setModuleService(ModuleService moduleService) { this.moduleService = moduleService; } /** * Add a managed module component to the registry of components. These will be controlled * by the {@link #startModules()} method. * * @param component a module component to be executed */ public synchronized void registerComponent(ModuleComponent component) { String moduleId = component.getModuleId(); String name = component.getName(); // Get the map of components for the module Map componentsByName = componentsByNameByModule.get(moduleId); if (componentsByName == null) { componentsByName = new HashMap(11); componentsByNameByModule.put(moduleId, componentsByName); } // Check if the component has already been registered if (componentsByName.containsKey(name)) { throw AlfrescoRuntimeException.create(ERR_COMPONENT_ALREADY_REGISTERED, name, moduleId); } // Add it componentsByName.put(name, component); // Done if (logger.isDebugEnabled()) { logger.debug("Registered component: " + component); } } /** * @return Returns the map of components keyed by name. The map could be empty but * will never be null. */ private synchronized Map getComponents(String moduleId) { Map componentsByName = componentsByNameByModule.get(moduleId); if (componentsByName != null) { // Done return componentsByName; } else { // Done return Collections.emptyMap(); } } /** * @inheritDoc */ public synchronized void startModules() { // Check properties PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); PropertyCheck.mandatory(this, "authenticationComponent", authenticationComponent); PropertyCheck.mandatory(this, "registryService", registryService); PropertyCheck.mandatory(this, "moduleService", moduleService); /* * Ensure transactionality and the correct authentication */ // Get the current authentication Authentication authentication = authenticationComponent.getCurrentAuthentication(); try { TransactionService transactionService = serviceRegistry.getTransactionService(); authenticationComponent.setSystemUserAsCurrentUser(); // Get all the modules List modules = moduleService.getAllModules(); loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); // Process each module in turn. Ordering is not important. final Set executedComponents = new HashSet(10); for (final ModuleDetails module : modules) { TransactionWork startModuleWork = new TransactionWork() { public Object doWork() throws Exception { startModule(module, executedComponents); return null; } }; TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, startModuleWork); } // Done if (logger.isDebugEnabled()) { logger.debug("Executed " + executedComponents.size() + " components"); } } finally { // Restore the original authentication authenticationComponent.setCurrentAuthentication(authentication); } } /** * Does the actual work without fussing about transactions and authentication. */ private void startModule(ModuleDetails module, Set executedComponents) { String moduleId = module.getId(); VersionNumber moduleVersion = module.getVersionNumber(); // 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); VersionNumber versionCurrent = (VersionNumber) registryService.getValue(moduleKeyCurrentVersion); String msg = null; if (versionCurrent == null) // There is no current version { msg = I18NUtil.getMessage(MSG_INSTALLING, moduleId, moduleVersion); // Record the install version registryService.addValue(moduleKeyInstalledVersion, moduleVersion); } else // It is an upgrade or is the same { if (versionCurrent.compareTo(moduleVersion) == 0) // The current version is the same { msg = I18NUtil.getMessage(MSG_STARTING, moduleId, moduleVersion); } else if (versionCurrent.compareTo(moduleVersion) > 0) // Downgrading not supported { throw AlfrescoRuntimeException.create(ERR_NO_DOWNGRADE, moduleId, versionCurrent, moduleVersion); } else // This is an upgrade { msg = I18NUtil.getMessage(MSG_UPGRADING, moduleId, moduleVersion, versionCurrent); } } loggerService.info(msg); // Record the current version registryService.addValue(moduleKeyCurrentVersion, moduleVersion); Map componentsByName = getComponents(moduleId); for (ModuleComponent component : componentsByName.values()) { executeComponent(module, component, executedComponents); } // Done if (logger.isDebugEnabled()) { logger.debug("Started module: " + module); } } /** * Execute the component, respecting dependencies. */ private void executeComponent(ModuleDetails module, 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; } // Check the version applicability VersionNumber moduleVersion = module.getVersionNumber(); VersionNumber minVersion = component.getAppliesFromVersionNumber(); VersionNumber maxVersion = component.getAppliesToVersionNumber(); if (moduleVersion.compareTo(minVersion) < 0 || moduleVersion.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 current version: \n" + " Component: " + component + "\n" + " Module: " + module + "\n" + " Version: " + moduleVersion + "\n" + " Applies From : " + minVersion + "\n" + " Applies To : " + maxVersion); } return; } // Construct the registry key to store the execution date String moduleId = component.getModuleId(); 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.getValue(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(module, dependency, executedComponents); } // Execute the component itself component.execute(); // Keep track of it in the registry and in this run executedComponents.add(component); registryService.addValue(executionDateKey, new Date()); // Done if (logger.isDebugEnabled()) { logger.debug("Executed module component: \n" + " Component: " + component); } } }