AMP dependency checking on the tool side.

This is work in progress and the repo is not currently double checking the runtime dependencies.

To declare a dependency, add the following to your module.properties:
   module.depends.ABC=ABCfromVersion - ABCtoVersion
Wildcard * can be used in place of fromVersion or toVersion, as well as just a single version.
The most common usage will be:
   module.depends.ABC=1.0-*
i.e. this module depends on ABC version 1.0 or later being present.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5601 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-05-02 20:28:20 +00:00
parent a6531ab4de
commit 8307ba5ba9
9 changed files with 430 additions and 87 deletions

View File

@@ -32,9 +32,11 @@ import java.util.Properties;
import java.util.StringTokenizer;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.module.ModuleDependency;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleInstallState;
import org.alfresco.util.ISO8601DateFormat;
import org.alfresco.util.Pair;
import org.alfresco.util.VersionNumber;
/**
@@ -49,6 +51,8 @@ import org.alfresco.util.VersionNumber;
*/
public class ModuleDetailsImpl implements ModuleDetails
{
private static final long serialVersionUID = 5782747774317351424L;
private String id;
private List<String> aliases;
private VersionNumber version;
@@ -56,6 +60,7 @@ public class ModuleDetailsImpl implements ModuleDetails
private String description;
private VersionNumber repoVersionMin;
private VersionNumber repoVersionMax;
private List<ModuleDependency> dependencies;
private Date installDate;
private ModuleInstallState installState;
@@ -67,6 +72,7 @@ public class ModuleDetailsImpl implements ModuleDetails
aliases = new ArrayList<String>(0);
repoVersionMin = VersionNumber.VERSION_ZERO;
repoVersionMax = VersionNumber.VERSION_BIG;
dependencies = new ArrayList<ModuleDependency>(0);
this.installState = ModuleInstallState.UNKNOWN;
}
@@ -151,6 +157,8 @@ public class ModuleDetailsImpl implements ModuleDetails
{
repoVersionMax = new VersionNumber(trimmedProperties.getProperty(PROP_REPO_VERSION_MAX));
}
// DEPENDENCIES
this.dependencies = extractDependencies(trimmedProperties);
// INSTALL DATE
if (trimmedProperties.getProperty(PROP_INSTALL_DATE) != null)
{
@@ -212,6 +220,34 @@ public class ModuleDetailsImpl implements ModuleDetails
this.title = title;
this.description = description;
}
private static List<ModuleDependency> extractDependencies(Properties properties)
{
int prefixLength = PROP_DEPENDS_PREFIX.length();
List<ModuleDependency> dependencies = new ArrayList<ModuleDependency>(2);
for (Map.Entry entry : properties.entrySet())
{
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (!key.startsWith(PROP_DEPENDS_PREFIX))
{
continue;
}
if (key.length() == prefixLength)
{
// Just ignore it
continue;
}
String dependencyId = key.substring(prefixLength);
// Build the dependency
ModuleDependency dependency = new ModuleDependencyImpl(dependencyId, value);
// Add it
dependencies.add(dependency);
}
// Done
return dependencies;
}
public Properties getProperties()
{
@@ -230,6 +266,15 @@ public class ModuleDetailsImpl implements ModuleDetails
{
properties.setProperty(PROP_REPO_VERSION_MAX, repoVersionMax.toString());
}
if (dependencies.size() > 0)
{
for (ModuleDependency dependency : dependencies)
{
String key = PROP_DEPENDS_PREFIX + dependency.getDependencyId();
String value = dependency.getVersionString();
properties.setProperty(key, value);
}
}
if (installDate != null)
{
String installDateStr = ISO8601DateFormat.format(installDate);
@@ -310,6 +355,11 @@ public class ModuleDetailsImpl implements ModuleDetails
this.repoVersionMax = repoVersionMax;
}
public List<ModuleDependency> getDependencies()
{
return dependencies;
}
public Date getInstallDate()
{
return installDate;
@@ -329,4 +379,164 @@ public class ModuleDetailsImpl implements ModuleDetails
{
this.installState = installState;
}
/**
* @author Derek Hulley
*/
public static final class ModuleDependencyImpl implements ModuleDependency
{
private static final long serialVersionUID = -6850832632316987487L;
private String dependencyId;
private String versionStr;
private List<Pair<VersionNumber, VersionNumber>> versionRanges;
private ModuleDependencyImpl(String dependencyId, String versionStr)
{
this.dependencyId = dependencyId;
this.versionStr = versionStr;
try
{
versionRanges = buildVersionRanges(versionStr);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Unable to interpret the module version ranges: " + versionStr, e);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(dependencyId).append(":").append(versionStr);
return sb.toString();
}
private static List<Pair<VersionNumber, VersionNumber>> buildVersionRanges(String versionStr)
{
List<Pair<VersionNumber, VersionNumber>> versionRanges = new ArrayList<Pair<VersionNumber, VersionNumber>>(1);
StringTokenizer rangesTokenizer = new StringTokenizer(versionStr, ",");
while (rangesTokenizer.hasMoreTokens())
{
String range = rangesTokenizer.nextToken().trim();
// Handle the * special case
if (range.equals("*"))
{
range = "*-*";
}
if (range.startsWith("-"))
{
range = "*" + range;
}
if (range.endsWith("-"))
{
range = range + "*";
}
// The range must have at least one version in it
StringTokenizer rangeTokenizer = new StringTokenizer(range, "-", false);
VersionNumber versionLower = null;
VersionNumber versionUpper = null;
while (rangeTokenizer.hasMoreTokens())
{
String version = rangeTokenizer.nextToken();
version = version.trim();
if (versionLower == null)
{
if (version.equals("*"))
{
// Unbounded lower version
versionLower = VersionNumber.VERSION_ZERO;
}
else
{
// Explicit lower bound
versionLower = new VersionNumber(version);
}
}
else if (versionUpper == null)
{
if (version.equals("*"))
{
// Unbounded upper version
versionUpper = VersionNumber.VERSION_BIG;
}
else
{
// Explicit upper bound
versionUpper = new VersionNumber(version);
}
}
}
// Check
if (versionUpper == null && versionLower == null)
{
throw new AlfrescoRuntimeException(
"Valid dependency version ranges are: \n" +
" LOW - HIGH \n" +
" * - HIGH \n" +
" LOW - * \n" +
" * ");
}
else if (versionUpper == null && versionLower != null)
{
versionUpper = versionLower;
}
else if (versionLower == null && versionUpper != null)
{
versionLower = versionUpper;
}
// Create the range pair
Pair<VersionNumber, VersionNumber> rangePair = new Pair<VersionNumber, VersionNumber>(versionLower, versionUpper);
versionRanges.add(rangePair);
}
return versionRanges;
}
public String getDependencyId()
{
return dependencyId;
}
public String getVersionString()
{
return versionStr;
}
public boolean isValidDependency(ModuleDetails moduleDetails)
{
// Nothing to compare to
if (moduleDetails == null)
{
return false;
}
// Check the ID
if (!moduleDetails.getId().equals(dependencyId))
{
return false;
}
// Check the version number
VersionNumber checkVersion = moduleDetails.getVersion();
boolean matched = false;
for (Pair<VersionNumber, VersionNumber> versionRange : versionRanges)
{
VersionNumber versionLower = versionRange.getFirst();
VersionNumber versionUpper = versionRange.getSecond();
if (checkVersion.compareTo(versionLower) < 0)
{
// The version is too low
continue;
}
if (checkVersion.compareTo(versionUpper) > 0)
{
// The version is too high
continue;
}
// It is a match
matched = true;
break;
}
return matched;
}
}
}

View File

@@ -24,9 +24,11 @@
*/
package org.alfresco.repo.module;
import java.util.List;
import java.util.Properties;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.module.ModuleDependency;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleInstallState;
import org.alfresco.util.VersionNumber;
@@ -53,6 +55,14 @@ public class ModuleDetailsImplTest extends TestCase
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_DEPENDS_PREFIX + "a", "1.2.3");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "b", "*");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "c", "- 1.2");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "d", "1.2 -");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "e", "* - 1.2");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "f", "1.2 - *");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "g", "0.5, 0.6");
defaultProperties.setProperty(ModuleDetails.PROP_DEPENDS_PREFIX + "h", "0.5 - 0.6, 0.9 - *");
defaultProperties.setProperty(ModuleDetails.PROP_INSTALL_STATE, ModuleInstallState.INSTALLED.toString());
}
@@ -71,6 +81,29 @@ public class ModuleDetailsImplTest extends TestCase
assertEquals("The properties are different", defaultProperties, processedProperties);
}
public void testDependencyChecks()
{
ModuleDetails details = new ModuleDetailsImpl(defaultProperties);
Properties tempProperties = new Properties();
tempProperties.setProperty(ModuleDetails.PROP_ID, "a");
tempProperties.setProperty(ModuleDetails.PROP_TITLE, "A");
tempProperties.setProperty(ModuleDetails.PROP_DESCRIPTION, "A description");
tempProperties.setProperty(ModuleDetails.PROP_VERSION, "1.0.0");
ModuleDetails tempDetails = new ModuleDetailsImpl(tempProperties);
List<ModuleDependency> dependencies = details.getDependencies();
assertEquals("Incorrect number of dependencies", 8, dependencies.size());
for (ModuleDependency dependency : dependencies)
{
if (dependency.getDependencyId().equals(tempDetails.getId()))
{
// It should not match
assertFalse("No match expected", dependency.isValidDependency(tempDetails));
}
}
}
public void testTrimming() throws Exception
{
defaultProperties.setProperty(ModuleDetails.PROP_INSTALL_STATE, " ");

View File

@@ -30,12 +30,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.module.ModuleDependency;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleInstallState;
import org.alfresco.util.VersionNumber;
@@ -127,25 +129,17 @@ public class ModuleManagementTool
{
this.verbose = verbose;
}
/**
* Installs all modules within a folder into the given WAR file.
*
* @param directory
* @param warFileLocation
* @see #installModule(String, String, boolean, boolean, boolean)
*/
public void installModules(String directory, String warFileLocation)
{
installModules(directory, warFileLocation, false, false, true);
}
/**
*
* @param directoryLocation
* @param warFileLocation
* @param preview
* @param forceInstall
* @param backupWAR
*/
public void installModules(String directoryLocation, String warFileLocation, boolean preview, boolean forceInstall, boolean backupWAR)
{
java.io.File dir = new java.io.File(directoryLocation);
@@ -159,14 +153,6 @@ public class ModuleManagementTool
}
}
/**
*
* @param dir
* @param warFileLocation
* @param preview
* @param forceInstall
* @param backupWAR
*/
private void installModules(java.io.File dir, String warFileLocation, boolean preview, boolean forceInstall, boolean backupWAR)
{
java.io.File[] children = dir.listFiles();
@@ -256,6 +242,25 @@ public class ModuleManagementTool
String installingId = installingModuleDetails.getId();
VersionNumber installingVersion = installingModuleDetails.getVersion();
// Check that the target war has the necessary dependencies for this install
List<ModuleDependency> installingModuleDependencies = installingModuleDetails.getDependencies();
List<ModuleDependency> missingDependencies = new ArrayList<ModuleDependency>(0);
for (ModuleDependency dependency : installingModuleDependencies)
{
String dependencyId = dependency.getDependencyId();
ModuleDetails dependencyModuleDetails = ModuleDetailsHelper.createModuleDetailsFromWarAndId(warFileLocation, dependencyId);
// Check the dependency. The API specifies that a null returns false, so no null check is required
if (!dependency.isValidDependency(dependencyModuleDetails))
{
missingDependencies.add(dependency);
continue;
}
}
if (missingDependencies.size() > 0)
{
throw new ModuleManagementToolException("The following modules must first be installed: " + missingDependencies);
}
// Try to find an installed module by the ID
ModuleDetails installedModuleDetails = ModuleDetailsHelper.createModuleDetailsFromWarAndId(warFileLocation, installingId);
if (installedModuleDetails == null)
@@ -716,71 +721,69 @@ public class ModuleManagementTool
*/
public static void main(String[] args)
{
if (args.length >= 1)
if (args.length <= 1)
{
ModuleManagementTool manager = new ModuleManagementTool();
outputUsage();
return;
}
ModuleManagementTool manager = new ModuleManagementTool();
String operation = args[0];
if (operation.equals(OP_INSTALL) == true && args.length >= 3)
{
String aepFileLocation = args[1];
String warFileLocation = args[2];
boolean forceInstall = false;
boolean previewInstall = false;
boolean backup = true;
boolean directory = false;
String operation = args[0];
if (operation.equals(OP_INSTALL) == true && args.length >= 3)
{
String aepFileLocation = args[1];
String warFileLocation = args[2];
boolean forceInstall = false;
boolean previewInstall = false;
boolean backup = true;
boolean directory = false;
if (args.length > 3)
if (args.length > 3)
{
for (int i = 3; i < args.length; i++)
{
for (int i = 3; i < args.length; i++)
String option = args[i];
if (OPTION_VERBOSE.equals(option) == true)
{
String option = args[i];
if (OPTION_VERBOSE.equals(option) == true)
{
manager.setVerbose(true);
}
else if (OPTION_FORCE.equals(option) == true)
{
forceInstall = true;
}
else if (OPTION_PREVIEW.equals(option) == true)
{
previewInstall = true;
manager.setVerbose(true);
}
else if (OPTION_NOBACKUP.equals(option) == true)
{
backup = false;
}
else if (OPTION_DIRECTORY.equals(option) == true)
{
directory = true;
}
manager.setVerbose(true);
}
else if (OPTION_FORCE.equals(option) == true)
{
forceInstall = true;
}
else if (OPTION_PREVIEW.equals(option) == true)
{
previewInstall = true;
manager.setVerbose(true);
}
else if (OPTION_NOBACKUP.equals(option) == true)
{
backup = false;
}
else if (OPTION_DIRECTORY.equals(option) == true)
{
directory = true;
}
}
if (directory == false)
{
// Install the module
manager.installModule(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup);
}
else
{
// Install the modules from the directory
manager.installModules(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup);
}
}
else if (OP_LIST.equals(operation) == true && args.length == 2)
if (directory == false)
{
// List the installed modules
String warFileLocation = args[1];
manager.listModules(warFileLocation);
// Install the module
manager.installModule(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup);
}
else
{
outputUsage();
// Install the modules from the directory
manager.installModules(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup);
}
}
else if (OP_LIST.equals(operation) == true && args.length == 2)
{
// List the installed modules
String warFileLocation = args[1];
manager.listModules(warFileLocation);
}
else
{
outputUsage();

View File

@@ -45,9 +45,10 @@ import de.schlichtherle.io.FileOutputStream;
import de.schlichtherle.io.ZipDetector;
/**
* Module management tool unit test
* @see org.alfresco.repo.module.tool.ModuleManagementTool
*
* @author Roy Wetherall
* @author Derek Hulley
*/
public class ModuleManagementToolTest extends TestCase
{
@@ -61,11 +62,9 @@ public class ModuleManagementToolTest extends TestCase
manager.setVerbose(true);
String warLocation = getFileLocation(".war", "module/test.war");
String ampLocation = getFileLocation(".amp", "module/test.amp");
String ampLocation = getFileLocation(".amp", "module/test_v1.amp");
String ampV2Location = getFileLocation(".amp", "module/test_v2.amp");
System.out.println(warLocation);
// Initial install of module
this.manager.installModule(ampLocation, warLocation);
@@ -161,15 +160,47 @@ public class ModuleManagementToolTest extends TestCase
}
}
public void testDependencySuccess() throws Exception
{
manager.setVerbose(true);
String warLocation = getFileLocation(".war", "module/test.war");
String testAmpV1Location = getFileLocation(".amp", "module/test_v1.amp");
String testAmpV2Location = getFileLocation(".amp", "module/test_v2.amp");
String testAmpDepV1Location = getFileLocation(".amp", "module/dependent_on_test_v1.amp");
String testAmpDepV2Location = getFileLocation(".amp", "module/dependent_on_test_v2.amp");
// Install V1
this.manager.installModule(testAmpV1Location, warLocation, false, false, false);
// Install the module dependent on test_v1
this.manager.installModule(testAmpDepV1Location, warLocation, false, false, false);
try
{
// Attempt to upgrade the dependent module
this.manager.installModule(testAmpDepV2Location, warLocation, false, false, false);
fail("Failed to detect inadequate dependency on the test amp");
}
catch (ModuleManagementToolException e)
{
System.out.println("Expected: " + e.getMessage());
}
// Install the test_v2
this.manager.installModule(testAmpV2Location, warLocation, false, false, false);
// The dependent module should now go in
this.manager.installModule(testAmpDepV2Location, warLocation, false, false, false);
}
public void testPreviewInstall()
throws Exception
{
manager.setVerbose(true);
String warLocation = getFileLocation(".war", "module/test.war");
String ampLocation = getFileLocation(".amp", "module/test.amp");
System.out.println(warLocation);
String ampLocation = getFileLocation(".amp", "module/test_v1.amp");
// Initial install of module
this.manager.installModule(ampLocation, warLocation, true, false, true);
@@ -183,9 +214,7 @@ public class ModuleManagementToolTest extends TestCase
manager.setVerbose(true);
String warLocation = getFileLocation(".war", "module/test.war");
String ampLocation = getFileLocation(".amp", "module/test.amp");
System.out.println(warLocation);
String ampLocation = getFileLocation(".amp", "module/test_v1.amp");
// Initial install of module
this.manager.installModule(ampLocation, warLocation, false, false, false);
@@ -198,15 +227,13 @@ public class ModuleManagementToolTest extends TestCase
manager.setVerbose(true);
String warLocation = getFileLocation(".war", "module/test.war");
String ampLocation = getFileLocation(".amp", "module/test.amp");
String ampLocation = getFileLocation(".amp", "module/test_v1.amp");
String ampV2Location = getFileLocation(".amp", "module/test_v2.amp");
int index = ampV2Location.lastIndexOf(File.separator);
System.out.println(index);
String directoryLocation = ampV2Location.substring(0, index);
System.out.println(warLocation);
System.out.println(directoryLocation);
try
{
this.manager.installModules(directoryLocation, warLocation);
@@ -222,7 +249,7 @@ public class ModuleManagementToolTest extends TestCase
throws Exception
{
String warLocation = getFileLocation(".war", "module/test.war");
String ampLocation = getFileLocation(".amp", "module/test.amp");
String ampLocation = getFileLocation(".amp", "module/test_v1.amp");
this.manager.listModules(warLocation);
@@ -230,7 +257,7 @@ public class ModuleManagementToolTest extends TestCase
this.manager.listModules(warLocation);
}
private String getFileLocation(String extension, String location)
throws IOException
{

View File

@@ -0,0 +1,63 @@
/*
* 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.service.cmr.module;
import java.io.Serializable;
/**
* An ensapsulated module dependency. Since module dependencies may be range based and even
* unbounded, it is not possible to describe a dependency using a list of module version numbers.
* This class answers the
*
* @author Derek Hulley
*/
public interface ModuleDependency extends Serializable
{
/**
* Get the ID of the module that this dependency describes. The dependency
* may be upon specific versions or a range of versions. Nevertheless, the
* module given by the returned ID will be required in one version or another.
*
* @return Returns the ID of the module that this depends on
*/
public String getDependencyId();
/**
* @return Returns a string representation of the versions supported
*/
public String getVersionString();
/**
* Check if a module satisfies the dependency requirements.
*
* @param moduleDetails the module details of the dependency. This must be
* the details of the module with the correct
* {@link #getDependencyId() ID}. This may be <tt>null</tt>
* in which case <tt>false</tt> will always be returned.
* @return Returns true if the module satisfies the dependency
* requirements.
*/
public boolean isValidDependency(ModuleDetails moduleDetails);
}

View File

@@ -24,6 +24,7 @@
*/
package org.alfresco.service.cmr.module;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Properties;
@@ -36,7 +37,7 @@ import org.alfresco.util.VersionNumber;
* @author Roy Wetherall
* @since 2.0
*/
public interface ModuleDetails
public interface ModuleDetails extends Serializable
{
static final String PROP_ID = "module.id";
static final String PROP_ALIASES = "module.aliases";
@@ -45,6 +46,7 @@ public interface ModuleDetails
static final String PROP_DESCRIPTION = "module.description";
static final String PROP_REPO_VERSION_MIN = "module.repo.version.min";
static final String PROP_REPO_VERSION_MAX = "module.repo.version.max";
static final String PROP_DEPENDS_PREFIX = "module.depends.";
static final String PROP_INSTALL_DATE = "module.installDate";
static final String PROP_INSTALL_STATE = "module.installState";
@@ -110,6 +112,11 @@ public interface ModuleDetails
*/
void setRepoVersionMax(VersionNumber repoVersionMax);
/**
* @return Returns a list of module dependencies that must be present for this module
*/
List<ModuleDependency> getDependencies();
/**
* Get the modules install date
*