FIXED ALF-12541: AMP files need to be able to be pinned to specific "edition(s)" of Alfresco

It is now possible to specify a module.editions property (eg. community) which is checked by the MMT.
Also, the version is checked on install.
Also, started refactoring some of the code for better reuse.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33668 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gethin James
2012-02-06 11:11:42 +00:00
parent 31525f85c6
commit fdc9bdfe84
10 changed files with 406 additions and 47 deletions

View File

@@ -52,6 +52,7 @@ public class ModuleDetailsImpl implements ModuleDetails
private VersionNumber version;
private String title;
private String description;
private List<String> editions;
private VersionNumber repoVersionMin;
private VersionNumber repoVersionMax;
private List<ModuleDependency> dependencies;
@@ -153,6 +154,9 @@ public class ModuleDetailsImpl implements ModuleDetails
}
// DEPENDENCIES
this.dependencies = extractDependencies(trimmedProperties);
this.editions = extractEditions(trimmedProperties);
// INSTALL DATE
if (trimmedProperties.getProperty(PROP_INSTALL_DATE) != null)
{
@@ -215,6 +219,22 @@ public class ModuleDetailsImpl implements ModuleDetails
this.description = description;
}
private static List<String> extractEditions(Properties trimmedProperties)
{
List<String> specifiedEditions = null;
String editions = trimmedProperties.getProperty(PROP_EDITIONS);
if (editions != null)
{
specifiedEditions = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(editions, ",");
while (st.hasMoreTokens())
{
specifiedEditions.add(st.nextToken());
}
}
return specifiedEditions;
}
private static List<ModuleDependency> extractDependencies(Properties properties)
{
int prefixLength = PROP_DEPENDS_PREFIX.length();
@@ -374,6 +394,16 @@ public class ModuleDetailsImpl implements ModuleDetails
this.installState = installState;
}
public List<String> getEditions()
{
return editions;
}
public void setEditions(List<String> editions)
{
this.editions = editions;
}
/**
* @author Derek Hulley
*/

View File

@@ -47,6 +47,7 @@ public class ModuleDetailsImplTest extends TestCase
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_EDITIONS, "Community");
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");

View File

@@ -176,7 +176,7 @@ public class InstalledFiles
*/
public String getFilePathInWar()
{
return ModuleManagementTool.MODULE_DIR + "/" + this.moduleId + "/modifications.install";
return WarHelper.MODULE_NAMESPACE_DIR + "/" + this.moduleId + "/modifications.install";
}
/**

View File

@@ -127,7 +127,7 @@ public class ModuleDetailsHelper
*/
public static String getModulePropertiesFilePathInWar(String moduleId)
{
return ModuleManagementTool.MODULE_DIR + "/" + moduleId + "/" + "module.properties";
return WarHelper.MODULE_NAMESPACE_DIR + "/" + moduleId + WarHelper.MODULE_CONFIG_IN_WAR;
}
/**

View File

@@ -24,14 +24,11 @@ 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;
@@ -68,8 +65,7 @@ public class ModuleManagementTool
private static final String PROP_INHERIT_DEFAULT = "include.default";
/** Standard directories found in the alfresco war */
public static final String MODULE_DIR = "/WEB-INF/classes/alfresco/module";
public static final String BACKUP_DIR = MODULE_DIR + "/backup";
public static final String BACKUP_DIR = WarHelper.MODULE_NAMESPACE_DIR+ "/backup";
/** Operations and options supperted via the command line interface to this class */
private static final String OP_INSTALL = "install";
@@ -92,6 +88,8 @@ public class ModuleManagementTool
/** Indicates the current verbose setting */
private boolean verbose = false;
WarHelper warHelper = new WarHelperImpl();
/**
* Constructor
*/
@@ -201,11 +199,11 @@ public class ModuleManagementTool
try
{
outputMessage("Installing AMP '" + ampFileLocation + "' into WAR '" + warFileLocation + "'");
java.io.File theWar = new File(warFileLocation, DETECTOR_AMP_AND_WAR);
if (preview == false)
{
// Make sure the module and backup directory exisits in the WAR file
File moduleDir = new File(warFileLocation + MODULE_DIR, DETECTOR_AMP_AND_WAR);
File moduleDir = new File(warFileLocation + WarHelper.MODULE_NAMESPACE_DIR, DETECTOR_AMP_AND_WAR);
if (moduleDir.exists() == false)
{
moduleDir.mkdir();
@@ -242,45 +240,13 @@ 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);
}
//A series of checks
warHelper.checkCompatibleVersion(theWar, installingModuleDetails);
warHelper.checkCompatibleEdition(theWar, installingModuleDetails);
warHelper.checkModuleDependencies(theWar, installingModuleDetails);
// 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;
}
}
ModuleDetails installedModuleDetails = warHelper.getModuleDetailsOrAlias(theWar, installingModuleDetails);
// Now clean up the old instance
if (installedModuleDetails != null)
@@ -639,7 +605,7 @@ public class ModuleManagementTool
this.verbose = true;
try
{
File moduleDir = new File(warLocation + MODULE_DIR, DETECTOR_AMP_AND_WAR);
File moduleDir = new File(warLocation + WarHelper.MODULE_NAMESPACE_DIR, DETECTOR_AMP_AND_WAR);
if (moduleDir.exists() == false)
{
outputMessage("No modules are installed in this WAR file");

View File

@@ -0,0 +1,48 @@
package org.alfresco.repo.module.tool;
import java.io.File;
import org.alfresco.service.cmr.module.ModuleDetails;
/**
* Performs various actions on a war file or exploded war directory
*
* @author Gethin James
*/
public interface WarHelper
{
public static final String MODULE_NAMESPACE_DIR = "/WEB-INF/classes/alfresco/module";
public static final String MODULE_CONFIG_IN_WAR = "/module.properties";
/**
* Gets the module details or an available alias
* @param war a valid war file or exploded directory from a war
* @param installingModuleDetails
* @return ModuleDetails
*/
public ModuleDetails getModuleDetailsOrAlias(File war, ModuleDetails installingModuleDetails);
/**
* Checks the dependencies of this module
* @param war
* @param installingModuleDetails
*/
public void checkModuleDependencies(File war, ModuleDetails installingModuleDetails);
/**
* Checks to see if the module is compatible with the version of Alfresco.
*
* @param war a valid war file or exploded directory from a war
*/
public void checkCompatibleVersion(File war, ModuleDetails installingModuleDetails);
/**
* This checks to see if the module that is being installed is compatible with the war.
* If not module edition is specfied then it will just return. However, if an edition is specified and it doesn't match
* then an error is thrown.
* @param war a valid war file or exploded directory from a war
* @param installingModuleDetails
*/
public void checkCompatibleEdition(File war, ModuleDetails installingModuleDetails);
}

View File

@@ -0,0 +1,170 @@
package org.alfresco.repo.module.tool;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.alfresco.repo.module.ModuleDetailsImpl;
import org.alfresco.service.cmr.module.ModuleDependency;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.util.VersionNumber;
import de.schlichtherle.io.FileInputStream;
/**
* Performs logic for the Module Management Tool.
*
* @author Gethin James
*/
public class WarHelperImpl implements WarHelper
{
public static final String VERSION_PROPERTIES = "/WEB-INF/classes/alfresco/version.properties";
@Override
public void checkCompatibleVersion(File war, ModuleDetails installingModuleDetails)
{
//Version check
File propsFile = getFile(war, VERSION_PROPERTIES);
Properties warVers = loadProperties(propsFile);
VersionNumber warVersion = new VersionNumber(warVers.getProperty("version.major")+"."+warVers.getProperty("version.revision")+"."+warVers.getProperty("version.minor"));
if(warVersion.compareTo(installingModuleDetails.getRepoVersionMin())==-1) {
throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()+") must be installed on a repo version greater than "+installingModuleDetails.getRepoVersionMin());
}
if(warVersion.compareTo(installingModuleDetails.getRepoVersionMax())==1) {
throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()+") cannot be installed on a repo version greater than "+installingModuleDetails.getRepoVersionMax());
}
}
@Override
public void checkCompatibleEdition(File war, ModuleDetails installingModuleDetails)
{
List<String> installableEditions = installingModuleDetails.getEditions();
if (installableEditions != null && installableEditions.size() > 0) {
File propsFile = getFile(war, VERSION_PROPERTIES);
Properties warVers = loadProperties(propsFile);
String warEdition = warVers.getProperty("version.edition");
for (String edition : installableEditions)
{
if (warEdition.equalsIgnoreCase(edition))
{
return; //successful match.
}
}
throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()
+") can only be installed in one of the following editions"+installableEditions);
}
}
@Override
public void checkModuleDependencies(File war, ModuleDetails installingModuleDetails)
{
// 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 = getModuleDetails(war, 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);
}
}
@Override
public ModuleDetails getModuleDetailsOrAlias(File war, ModuleDetails installingModuleDetails)
{
ModuleDetails installedModuleDetails = getModuleDetails(war, installingModuleDetails.getId());
if (installedModuleDetails == null)
{
// It might be there as one of the aliases
List<String> installingAliases = installingModuleDetails.getAliases();
for (String installingAlias : installingAliases)
{
ModuleDetails installedAliasModuleDetails = getModuleDetails(war, 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 '" + installingModuleDetails + "'", false);
break;
}
}
return installedModuleDetails;
}
/**
* Gets the module details for the specified module from the war.
* @param war a valid war file or exploded directory from a war
* @param moduleId
* @return
*/
protected ModuleDetails getModuleDetails(File war, String moduleId)
{
ModuleDetails moduleDets = null;
File theFile = getModuleDetailsFile(war, moduleId);
if (theFile != null && theFile.exists())
{
moduleDets = new ModuleDetailsImpl(loadProperties(theFile));
}
return moduleDets;
}
/**
* Reads a .properites file from the war and returns it as a Properties object
* @param propertiesPath Path to the properties file (including .properties)
* @return Properties object or null
*/
private Properties loadProperties(File propertiesFile)
{
Properties result = null;
try
{
if (propertiesFile.exists())
{
InputStream is = new FileInputStream(propertiesFile);
result = new Properties();
result.load(is);
}
}
catch (IOException exception)
{
throw new ModuleManagementToolException("Unable to load properties from the war file; "+propertiesFile.getPath(), exception);
}
return result;
}
private File getModuleDetailsFile(File war, String moduleId)
{
return getFile(war,MODULE_NAMESPACE_DIR+ "/" + moduleId+MODULE_CONFIG_IN_WAR);
}
private File getFile(File war, String pathToFileIncludingExtension)
{
return new de.schlichtherle.io.File(war,pathToFileIncludingExtension, ModuleManagementTool.DETECTOR_AMP_AND_WAR);
}
}

View File

@@ -0,0 +1,134 @@
package org.alfresco.repo.module.tool;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import org.alfresco.repo.module.ModuleDetailsImpl;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.util.TempFileProvider;
import org.alfresco.util.VersionNumber;
import org.junit.Test;
import org.springframework.util.FileCopyUtils;
import de.schlichtherle.io.FileOutputStream;
/**
* Tests the war helper.
*
* @author Gethin James
*/
public class WarHelperImplTest extends WarHelperImpl
{
private WarHelper warhelper = new WarHelperImpl();
// @Before
// public void setUp() throws Exception
// {
// }
@Test
public void testCheckCompatibleVersion()
{
File theWar = getFile(".war", "module/test.war"); //Version 4.0.1
ModuleDetails installingModuleDetails = new ModuleDetailsImpl("test_it", new VersionNumber("9999"), "Test Mod", "Testing module");
installingModuleDetails.setRepoVersionMin(new VersionNumber("10.1"));
try
{
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails);
fail(); //should never get here
}
catch (ModuleManagementToolException exception)
{
assertTrue(exception.getMessage().endsWith("must be installed on a repo version greater than 10.1"));
}
installingModuleDetails.setRepoVersionMin(new VersionNumber("1.1"));
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails); //does not throw exception
installingModuleDetails.setRepoVersionMax(new VersionNumber("3.0"));
try
{
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails);
fail(); //should never get here
}
catch (ModuleManagementToolException exception)
{
assertTrue(exception.getMessage().endsWith("cannot be installed on a repo version greater than 3.0"));
}
installingModuleDetails.setRepoVersionMax(new VersionNumber("99"));
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails); //does not throw exception
installingModuleDetails.setRepoVersionMin(new VersionNumber("4.0.1")); //current war version
installingModuleDetails.setRepoVersionMax(new VersionNumber("4.0.1")); //current war version
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails); //does not throw exception
}
@Test
public void testCheckCompatibleEdition()
{
Properties props = new Properties();
props.setProperty(ModuleDetails.PROP_ID, "TestComp");
props.setProperty(ModuleDetails.PROP_VERSION, "9999");
props.setProperty(ModuleDetails.PROP_TITLE, "Test for Compatiblity");
props.setProperty(ModuleDetails.PROP_DESCRIPTION, "Test for Compatible Editions");
ModuleDetails installingModuleDetails = new ModuleDetailsImpl(props);
File theWar = getFile(".war", "module/test.war"); //Community Edition
//Test for no edition specified
this.warhelper.checkCompatibleEdition(theWar, installingModuleDetails); //does not throw exception
//Test for invalid edition
props.setProperty(ModuleDetails.PROP_EDITIONS, "CommuniT");
installingModuleDetails = new ModuleDetailsImpl(props);
try
{
this.warhelper.checkCompatibleEdition(theWar, installingModuleDetails);
fail(); //should never get here
}
catch (ModuleManagementToolException exception)
{
assertTrue(exception.getMessage().endsWith("can only be installed in one of the following editions[CommuniT]"));
}
props.setProperty(ModuleDetails.PROP_EDITIONS, ("CoMMunity")); //should ignore case
installingModuleDetails = new ModuleDetailsImpl(props);
this.warhelper.checkCompatibleEdition(theWar, installingModuleDetails); //does not throw exception
props.setProperty(ModuleDetails.PROP_EDITIONS, ("enterprise,community,bob")); //should ignore case
installingModuleDetails = new ModuleDetailsImpl(props);
this.warhelper.checkCompatibleEdition(theWar, installingModuleDetails); //does not throw exception
props.setProperty(ModuleDetails.PROP_EDITIONS, ("enterprise,Community")); //should ignore case
installingModuleDetails = new ModuleDetailsImpl(props);
this.warhelper.checkCompatibleVersion(theWar, installingModuleDetails); //does not throw exception
}
private File getFile(String extension, String location)
{
File file = TempFileProvider.createTempFile("moduleManagementToolTest-", extension);
InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);
assertNotNull(is);
OutputStream os;
try
{
os = new FileOutputStream(file);
FileCopyUtils.copy(is, os);
}
catch (IOException error)
{
error.printStackTrace();
}
return file;
}
}

View File

@@ -38,6 +38,7 @@ public interface ModuleDetails extends Serializable
static final String PROP_VERSION = "module.version";
static final String PROP_TITLE = "module.title";
static final String PROP_DESCRIPTION = "module.description";
static final String PROP_EDITIONS = "module.editions";
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.";
@@ -138,4 +139,13 @@ public interface ModuleDetails extends Serializable
* @param installState the module install state
*/
void setInstallState(ModuleInstallState installState);
List<String> getEditions();
/**
* Sets the editions of Alfresco the module is valid for
*
* @param edition comma seperated list of editions. e.g. community,Enterprise
*/
void setEditions(List<String> editions);
}