Support for renaming of modules

- When a module ID changes, the old ID gets put in a list against property 'module.aliases'.
 - The tool and the repo startup detect the existing installation against the alias and perform a rename.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5559 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-04-26 08:42:19 +00:00
parent d94db4e310
commit 31efa6d4f3
10 changed files with 362 additions and 46 deletions

View File

@@ -66,7 +66,6 @@ public class ModuleComponentHelper
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 REGISTRY_PROPERTY_DUMMY = "dummy";
private static final String MSG_FOUND_MODULES = "module.msg.found_modules";
private static final String MSG_STARTING = "module.msg.starting";
@@ -248,7 +247,7 @@ public class ModuleComponentHelper
// Get the IDs of all modules from the registry
RegistryKey moduleKeyAllIds = new RegistryKey(
ModuleComponentHelper.URI_MODULES_1_0,
REGISTRY_PATH_MODULES, REGISTRY_PROPERTY_DUMMY);
REGISTRY_PATH_MODULES, null);
Collection<String> moduleIds = registryService.getChildElements(moduleKeyAllIds);
// Check that each module is present in the distribution
@@ -268,13 +267,65 @@ public class ModuleComponentHelper
RegistryKey moduleKeyCurrentVersion = new RegistryKey(
ModuleComponentHelper.URI_MODULES_1_0,
REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION);
VersionNumber versionCurrent = (VersionNumber) registryService.getValue(moduleKeyCurrentVersion);
VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion);
// 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<String> 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<String> 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.
*/
@@ -283,7 +334,10 @@ public class ModuleComponentHelper
String moduleId = module.getId();
VersionNumber moduleVersion = module.getVersion();
// First check that the module version is fundamentall compatible with the repository
// 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();
@@ -303,13 +357,13 @@ public class ModuleComponentHelper
RegistryKey moduleKeyCurrentVersion = new RegistryKey(
ModuleComponentHelper.URI_MODULES_1_0,
REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION);
VersionNumber versionCurrent = (VersionNumber) registryService.getValue(moduleKeyCurrentVersion);
VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(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);
registryService.addProperty(moduleKeyInstalledVersion, moduleVersion);
}
else // It is an upgrade or is the same
{
@@ -328,7 +382,7 @@ public class ModuleComponentHelper
}
loggerService.info(msg);
// Record the current version
registryService.addValue(moduleKeyCurrentVersion, moduleVersion);
registryService.addProperty(moduleKeyCurrentVersion, moduleVersion);
Map<String, ModuleComponent> componentsByName = getComponents(moduleId);
for (ModuleComponent component : componentsByName.values())
@@ -387,7 +441,7 @@ public class ModuleComponentHelper
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);
Date executionDate = (Date) registryService.getProperty(executionDateKey);
if (executionDate != null && component.isExecuteOnceOnly())
{
// It has been executed and is scheduled for a single execution - leave it
@@ -410,7 +464,7 @@ public class ModuleComponentHelper
component.execute();
// Keep track of it in the registry and in this run
executedComponents.add(component);
registryService.addValue(executionDateKey, new Date());
registryService.addProperty(executionDateKey, new Date());
// Done
if (logger.isDebugEnabled())
{

View File

@@ -27,7 +27,9 @@ package org.alfresco.repo.module;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.module.ModuleDetails;
@@ -48,6 +50,7 @@ import org.alfresco.util.VersionNumber;
public class ModuleDetailsImpl implements ModuleDetails
{
private String id;
private List<String> aliases;
private VersionNumber version;
private String title;
private String description;
@@ -56,53 +59,102 @@ public class ModuleDetailsImpl implements ModuleDetails
private Date installDate;
private ModuleInstallState installState;
/**
/**
* Private constructor to set default values.
*/
private ModuleDetailsImpl()
{
aliases = new ArrayList<String>(0);
repoVersionMin = new VersionNumber("0.0.0");
repoVersionMax = new VersionNumber("999.999.999");
this.installState = ModuleInstallState.UNKNOWN;
}
/**
* Creates the instance from a set of properties. All the property values are trimmed
* and empty string values are removed from the set. In other words, zero length or
* whitespace strings are not supported.
*
* @param properties the set of properties
*/
public ModuleDetailsImpl(Properties properties)
{
// Set defaults
this();
// Copy the properties so they don't get modified
Properties trimmedProperties = new Properties();
// Trim all the property values
for (Map.Entry entry : properties.entrySet())
{
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (value == null)
{
// Don't copy nulls over
continue;
}
String trimmedValue = value.trim();
if (trimmedValue.length() == 0)
{
// Don't copy empty strings over
continue;
}
// It is a real value
trimmedProperties.setProperty(key, trimmedValue);
}
// Check that the required properties are present
List<String> missingProperties = new ArrayList<String>(1);
// ID
id = properties.getProperty(PROP_ID);
if (id == null) { missingProperties.add(PROP_ID); }
id = trimmedProperties.getProperty(PROP_ID);
if (id == null)
{
missingProperties.add(PROP_ID);
}
// ALIASES
String aliasesStr = trimmedProperties.getProperty(PROP_ALIASES);
if (aliasesStr != null)
{
StringTokenizer st = new StringTokenizer(aliasesStr, ",");
while (st.hasMoreTokens())
{
String alias = st.nextToken().trim();
if (alias.length() == 0)
{
continue;
}
aliases.add(alias);
}
}
// VERSION
if (properties.getProperty(PROP_VERSION) == null)
if (trimmedProperties.getProperty(PROP_VERSION) == null)
{
missingProperties.add(PROP_VERSION);
}
else
{
version = new VersionNumber(properties.getProperty(PROP_VERSION));
version = new VersionNumber(trimmedProperties.getProperty(PROP_VERSION));
}
// TITLE
title = properties.getProperty(PROP_TITLE);
title = trimmedProperties.getProperty(PROP_TITLE);
if (title == null) { missingProperties.add(PROP_TITLE); }
// DESCRIPTION
description = properties.getProperty(PROP_DESCRIPTION);
description = trimmedProperties.getProperty(PROP_DESCRIPTION);
if (description == null) { missingProperties.add(PROP_DESCRIPTION); }
// REPO MIN
if (properties.getProperty(PROP_REPO_VERSION_MIN) == null)
if (trimmedProperties.getProperty(PROP_REPO_VERSION_MIN) != null)
{
repoVersionMin = new VersionNumber("0.0.0");
}
else
{
repoVersionMin = new VersionNumber(properties.getProperty(PROP_REPO_VERSION_MIN));
repoVersionMin = new VersionNumber(trimmedProperties.getProperty(PROP_REPO_VERSION_MIN));
}
// REPO MAX
if (properties.getProperty(PROP_REPO_VERSION_MAX) == null)
if (trimmedProperties.getProperty(PROP_REPO_VERSION_MAX) != null)
{
repoVersionMax = new VersionNumber("999.999.999");
}
else
{
repoVersionMax = new VersionNumber(properties.getProperty(PROP_REPO_VERSION_MAX));
repoVersionMax = new VersionNumber(trimmedProperties.getProperty(PROP_REPO_VERSION_MAX));
}
// INSTALL DATE
if (properties.getProperty(PROP_INSTALL_DATE) != null)
if (trimmedProperties.getProperty(PROP_INSTALL_DATE) != null)
{
String installDateStr = properties.getProperty(PROP_INSTALL_DATE);
String installDateStr = trimmedProperties.getProperty(PROP_INSTALL_DATE);
try
{
installDate = ISO8601DateFormat.parse(installDateStr);
@@ -112,6 +164,19 @@ public class ModuleDetailsImpl implements ModuleDetails
throw new AlfrescoRuntimeException("Unable to parse install date: " + installDateStr, e);
}
}
// INSTALL STATE
if (trimmedProperties.getProperty(PROP_INSTALL_STATE) != null)
{
String installStateStr = trimmedProperties.getProperty(PROP_INSTALL_STATE);
try
{
installState = ModuleInstallState.valueOf(installStateStr);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Unable to parse install state: " + installStateStr, e);
}
}
// Check
if (missingProperties.size() > 0)
{
@@ -124,9 +189,11 @@ public class ModuleDetailsImpl implements ModuleDetails
" Min repo version: " + repoVersionMin + "\n" +
" Max repo version: " + repoVersionMax);
}
// Set other defaults
installState = ModuleInstallState.INSTALLED;
if (id.matches(INVALID_ID_REGEX))
{
throw new AlfrescoRuntimeException(
"The module ID '" + id + "' is invalid. It may consist of valid characters, numbers, '.', '_' and '-'");
}
}
/**
@@ -137,6 +204,9 @@ public class ModuleDetailsImpl implements ModuleDetails
*/
public ModuleDetailsImpl(String id, VersionNumber versionNumber, String title, String description)
{
// Set defaults
this();
this.id = id;
this.version = versionNumber;
this.title = title;
@@ -170,6 +240,21 @@ public class ModuleDetailsImpl implements ModuleDetails
String installStateStr = installState.toString();
properties.setProperty(PROP_INSTALL_STATE, installStateStr);
}
if (aliases.size() > 0)
{
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String oldId : aliases)
{
if (!first)
{
sb.append(", ");
}
sb.append(oldId);
first = false;
}
properties.setProperty(PROP_ALIASES, sb.toString());
}
// Done
return properties;
}
@@ -185,6 +270,11 @@ public class ModuleDetailsImpl implements ModuleDetails
return id;
}
public List<String> getAliases()
{
return aliases;
}
public VersionNumber getVersion()
{
return version;

View File

@@ -0,0 +1,108 @@
/*
* 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 java.util.Properties;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleInstallState;
import org.alfresco.util.VersionNumber;
import junit.framework.TestCase;
/**
* @see org.alfresco.repo.module.ModuleDetailsImpl
*
* @author Derek Hulley
*/
public class ModuleDetailsImplTest extends TestCase
{
private Properties defaultProperties;
@Override
protected void setUp() throws Exception
{
defaultProperties = new Properties();
defaultProperties.setProperty(ModuleDetails.PROP_ID, "org.alfresco.module.Test");
defaultProperties.setProperty(ModuleDetails.PROP_ALIASES, "test, Test");
defaultProperties.setProperty(ModuleDetails.PROP_TITLE, "Test");
defaultProperties.setProperty(ModuleDetails.PROP_DESCRIPTION, "Test description");
defaultProperties.setProperty(ModuleDetails.PROP_VERSION, "1.0.0");
defaultProperties.setProperty(ModuleDetails.PROP_REPO_VERSION_MIN, new VersionNumber("1.2").toString());
defaultProperties.setProperty(ModuleDetails.PROP_REPO_VERSION_MAX, new VersionNumber("1.4.3").toString());
defaultProperties.setProperty(ModuleDetails.PROP_INSTALL_STATE, ModuleInstallState.INSTALLED.toString());
}
@SuppressWarnings("unused")
public void testDefaults()
{
ModuleDetails details = new ModuleDetailsImpl(defaultProperties);
}
public void testWriteAndReadProperties()
{
ModuleDetails details = new ModuleDetailsImpl(defaultProperties);
// convert back to properties
Properties processedProperties = details.getProperties();
assertEquals("The number of properties changed", defaultProperties.size(), processedProperties.size());
assertEquals("The properties are different", defaultProperties, processedProperties);
}
public void testTrimming() throws Exception
{
defaultProperties.setProperty(ModuleDetails.PROP_INSTALL_STATE, " ");
ModuleDetails details = new ModuleDetailsImpl(defaultProperties);
assertEquals("Expected the install state to be UNKNOWN", ModuleInstallState.UNKNOWN, details.getInstallState());
}
public void testInvalidIds() throws Exception
{
String[] invalidIds = new String[] {"", " ", "$", "module$Test", "module.Test$", "org alfresco module Test"};
for (String invalidId : invalidIds)
{
try
{
defaultProperties.setProperty(ModuleDetails.PROP_ID, invalidId);
new ModuleDetailsImpl(defaultProperties);
fail("Invalid ID not detected: " + invalidId);
}
catch (AlfrescoRuntimeException e)
{
// Expected
}
}
}
public void testValidIds() throws Exception
{
String[] validIds = new String[] {"abc123", " abc123 ", "a-b-c", "a.b.c", "a_b_c", "A.1.2.3"};
for (String validId : validIds)
{
defaultProperties.setProperty(ModuleDetails.PROP_ID, validId);
new ModuleDetailsImpl(defaultProperties);
}
}
}

View File

@@ -74,7 +74,6 @@ public class ModuleServiceImpl implements ModuleService
private static Log logger = LogFactory.getLog(ModuleServiceImpl.class);
private ServiceRegistry serviceRegistry;
private DescriptorService descriptorService;
private AuthenticationComponent authenticationComponent;
private ModuleComponentHelper moduleComponentHelper;
/** A cache of module details by module ID */
@@ -95,7 +94,6 @@ public class ModuleServiceImpl implements ModuleService
public void setDescriptorService(DescriptorService descriptorService)
{
this.descriptorService = descriptorService;
this.moduleComponentHelper.setDescriptorService(descriptorService);
}

View File

@@ -148,9 +148,17 @@ public class InstalledFiles
*
* @return the file location
*/
private String getFileLocation()
public String getFileLocation()
{
return this.warLocation + ModuleManagementTool.MODULE_DIR + "/" + this.moduleId + "/modifications.install";
return this.warLocation + getFilePathInWar();
}
/**
* @return Returns the path of the install file within the WAR
*/
public String getFilePathInWar()
{
return ModuleManagementTool.MODULE_DIR + "/" + this.moduleId + "/modifications.install";
}
/**

View File

@@ -75,7 +75,7 @@ public class ModuleDetailsHelper
try
{
File file = new File(location, ModuleManagementTool.DETECTOR_AMP_AND_WAR);
if (file.exists() == true)
if (file.exists())
{
InputStream is = new FileInputStream(file);
result = createModuleDetailsFromPropertiesStream(is);
@@ -102,6 +102,19 @@ public class ModuleDetailsHelper
return ModuleDetailsHelper.createModuleDetailsFromPropertyLocation(modulePropertiesFileLocation);
}
/**
* @param warLocation the location of the WAR file
* @param moduleId the module ID within the WAR
* @return Returns a file handle to the module properties file within the given WAR.
* The file may or may not exist.
*/
public static File getModuleDetailsFileFromWarAndId(String warLocation, String moduleId)
{
String location = ModuleDetailsHelper.getModulePropertiesFileLocation(warLocation, moduleId);
File file = new File(location, ModuleManagementTool.DETECTOR_AMP_AND_WAR);
return file;
}
/**
* Gets the file location
*
@@ -109,9 +122,18 @@ public class ModuleDetailsHelper
* @param moduleId the module id
* @return the file location
*/
private static String getModulePropertiesFileLocation(String warLocation, String moduleId)
public static String getModulePropertiesFileLocation(String warLocation, String moduleId)
{
return warLocation + ModuleManagementTool.MODULE_DIR + "/" + moduleId + "/" + "module.properties";
return warLocation + getModulePropertiesFilePathInWar(moduleId);
}
/**
* @param moduleId the module ID
* @return Returns the path of the module file within a WAR
*/
public static String getModulePropertiesFilePathInWar(String moduleId)
{
return ModuleManagementTool.MODULE_DIR + "/" + moduleId + "/" + "module.properties";
}
/**

View File

@@ -31,6 +31,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -229,7 +230,7 @@ public class ModuleManagementTool
backUpDir.mkdir();
}
// Make a backup of the war we are oging to modify
// Make a backup of the war we are going to modify
if (backupWAR == true)
{
java.io.File warFile = new java.io.File(warFileLocation);
@@ -255,8 +256,28 @@ public class ModuleManagementTool
String installingId = installingModuleDetails.getId();
VersionNumber installingVersion = installingModuleDetails.getVersion();
// Get the detail of the installed module
ModuleDetails installedModuleDetails = ModuleDetailsHelper.createModuleDetailsFromWarAndId(warFileLocation, installingModuleDetails.getId());
// Try to find an installed module by the ID
ModuleDetails installedModuleDetails = ModuleDetailsHelper.createModuleDetailsFromWarAndId(warFileLocation, installingId);
if (installedModuleDetails == null)
{
// It might be there as one of the aliases
List<String> installingAliases = installingModuleDetails.getAliases();
for (String installingAlias : installingAliases)
{
ModuleDetails installedAliasModuleDetails = ModuleDetailsHelper.createModuleDetailsFromWarAndId(warFileLocation, installingAlias);
if (installedAliasModuleDetails == null)
{
// There is nothing by that alias
continue;
}
// We found an alias and will treat it as the same module
installedModuleDetails = installedAliasModuleDetails;
outputMessage("Module '" + installingAlias + "' is installed and is an alias of '" + installingId + "'");
break;
}
}
// Now clean up the old instance
if (installedModuleDetails != null)
{
String installedId = installedModuleDetails.getId();
@@ -453,6 +474,12 @@ public class ModuleManagementTool
outputMessage("Recovering file '" + update.getKey() + "' from backup '" + update.getValue() + "'", true);
}
// Now remove the installed files list
String installedFilesPathInWar = installedFiles.getFilePathInWar();
removeFile(warFileLocation, installedFilesPathInWar, preview);
// Remove the module properties
String modulePropertiesFileLocationInWar = ModuleDetailsHelper.getModulePropertiesFilePathInWar(moduleId);
removeFile(warFileLocation, modulePropertiesFileLocationInWar, preview);
}
/**
@@ -478,7 +505,7 @@ public class ModuleManagementTool
outputMessage("The file '" + filePath + "' was expected for removal but was not present in the war", true);
}
}
/**
* Copies a file from the AMP location to the correct location in the WAR, interating on directories where appropraite.
*

View File

@@ -1,4 +1,4 @@
# The default AEP => WAR file mappings
# The default AMP => WAR file mappings
/config=/WEB-INF/classes
/lib=/WEB-INF/lib
/licenses=/WEB-INF/licenses

View File

@@ -25,6 +25,7 @@
package org.alfresco.service.cmr.module;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import org.alfresco.util.VersionNumber;
@@ -38,6 +39,7 @@ import org.alfresco.util.VersionNumber;
public interface ModuleDetails
{
static final String PROP_ID = "module.id";
static final String PROP_ALIASES = "module.aliases";
static final String PROP_VERSION = "module.version";
static final String PROP_TITLE = "module.title";
static final String PROP_DESCRIPTION = "module.description";
@@ -46,6 +48,8 @@ public interface ModuleDetails
static final String PROP_INSTALL_DATE = "module.installDate";
static final String PROP_INSTALL_STATE = "module.installState";
static final String INVALID_ID_REGEX = ".*[^\\w.-].*";
/**
* Get all defined properties.
*
@@ -60,6 +64,11 @@ public interface ModuleDetails
*/
String getId();
/**
* @return Returns a list of IDs by which this module may once have been known
*/
List<String> getAliases();
/**
* Get the version number of the module
*

View File

@@ -32,7 +32,7 @@ package org.alfresco.service.cmr.module;
public enum ModuleInstallState
{
/** The state of the module is unknown */
UKNOWN,
UNKNOWN,
/** The module is installed */
INSTALLED,
/** The module is disabled */