diff --git a/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java b/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java index fcf7576d7b..f4a31cda1f 100644 --- a/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java +++ b/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java @@ -52,6 +52,7 @@ public class ModuleDetailsImpl implements ModuleDetails private VersionNumber version; private String title; private String description; + private List editions; private VersionNumber repoVersionMin; private VersionNumber repoVersionMax; private List 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 extractEditions(Properties trimmedProperties) + { + List specifiedEditions = null; + String editions = trimmedProperties.getProperty(PROP_EDITIONS); + if (editions != null) + { + specifiedEditions = new ArrayList(); + StringTokenizer st = new StringTokenizer(editions, ","); + while (st.hasMoreTokens()) + { + specifiedEditions.add(st.nextToken()); + } + } + return specifiedEditions; + } + private static List extractDependencies(Properties properties) { int prefixLength = PROP_DEPENDS_PREFIX.length(); @@ -374,6 +394,16 @@ public class ModuleDetailsImpl implements ModuleDetails this.installState = installState; } + public List getEditions() + { + return editions; + } + + public void setEditions(List editions) + { + this.editions = editions; + } + /** * @author Derek Hulley */ diff --git a/source/java/org/alfresco/repo/module/ModuleDetailsImplTest.java b/source/java/org/alfresco/repo/module/ModuleDetailsImplTest.java index db02095420..af663fdd71 100644 --- a/source/java/org/alfresco/repo/module/ModuleDetailsImplTest.java +++ b/source/java/org/alfresco/repo/module/ModuleDetailsImplTest.java @@ -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"); diff --git a/source/java/org/alfresco/repo/module/tool/InstalledFiles.java b/source/java/org/alfresco/repo/module/tool/InstalledFiles.java index e53916d9c1..f0a291a4e3 100644 --- a/source/java/org/alfresco/repo/module/tool/InstalledFiles.java +++ b/source/java/org/alfresco/repo/module/tool/InstalledFiles.java @@ -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"; } /** diff --git a/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java index 26c61c3644..3a8f5f6b14 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java @@ -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; } /** diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java index 905a3f8130..e44b089f3a 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java @@ -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 installingModuleDependencies = installingModuleDetails.getDependencies(); - List missingDependencies = new ArrayList(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 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"); diff --git a/source/java/org/alfresco/repo/module/tool/WarHelper.java b/source/java/org/alfresco/repo/module/tool/WarHelper.java new file mode 100644 index 0000000000..79f8c5b50a --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/WarHelper.java @@ -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); + +} diff --git a/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java b/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java new file mode 100644 index 0000000000..889caf36fe --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/WarHelperImpl.java @@ -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 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 installingModuleDependencies = installingModuleDetails.getDependencies(); + List missingDependencies = new ArrayList(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 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); + } + + + + +} diff --git a/source/java/org/alfresco/repo/module/tool/WarHelperImplTest.java b/source/java/org/alfresco/repo/module/tool/WarHelperImplTest.java new file mode 100644 index 0000000000..0ded4ca9b9 --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/WarHelperImplTest.java @@ -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; +} +} diff --git a/source/java/org/alfresco/service/cmr/module/ModuleDetails.java b/source/java/org/alfresco/service/cmr/module/ModuleDetails.java index f52b57ae9b..4e024f623b 100644 --- a/source/java/org/alfresco/service/cmr/module/ModuleDetails.java +++ b/source/java/org/alfresco/service/cmr/module/ModuleDetails.java @@ -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 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 editions); } diff --git a/source/test-resources/module/test.war b/source/test-resources/module/test.war index efb5ef2718..803d424481 100644 Binary files a/source/test-resources/module/test.war and b/source/test-resources/module/test.war differ