diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml
index b4377cf42e..5ca124e1c2 100644
--- a/config/alfresco/application-context.xml
+++ b/config/alfresco/application-context.xml
@@ -43,5 +43,9 @@
-->
+
+
+
+
diff --git a/source/java/org/alfresco/repo/module/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/ModuleManagementTool.java
new file mode 100644
index 0000000000..c1d8134330
--- /dev/null
+++ b/source/java/org/alfresco/repo/module/ModuleManagementTool.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2005 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.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.util.VersionNumber;
+import org.apache.log4j.Logger;
+
+import de.schlichtherle.io.DefaultRaesZipDetector;
+import de.schlichtherle.io.File;
+import de.schlichtherle.io.FileInputStream;
+import de.schlichtherle.io.FileOutputStream;
+import de.schlichtherle.io.ZipControllerException;
+import de.schlichtherle.io.ZipDetector;
+import de.schlichtherle.io.ZipWarningException;
+
+/**
+ * @author Roy Wetherall
+ */
+public class ModuleManagementTool
+{
+ public static Logger logger = Logger.getLogger("org.alfresco.repo.extension.ModuleManagementTool");
+
+ private static final String DEFAULT_FILE_MAPPING_PROPERTIES = "org/alfresco/repo/module/default-file-mapping.properties";
+ private static final String MODULE_DIR = "/WEB-INF/classes/alfresco/module";
+
+ private static final String DELIMITER = ":";
+
+ private static final String PROP_ID = "module.id";
+ private static final String PROP_TITLE = "module.title";
+ private static final String PROP_DESCRIPTION = "module.description";
+ private static final String PROP_VERSION = "module.version";
+
+ private static final String MOD_ADD_FILE = "add";
+ private static final String MOD_UPDATE_FILE = "update";
+ private static final String MOD_MK_DIR = "mkdir";
+
+ private static final String OP_INSTALL = "install";
+
+ private ZipDetector defaultDetector;
+
+ private Properties fileMappingProperties;
+
+ private boolean verbose = false;
+
+ public ModuleManagementTool()
+ {
+ // Create the default zip detector
+ this.defaultDetector = new DefaultRaesZipDetector("amp|war");
+
+ // Load the default file mapping properties
+ this.fileMappingProperties = new Properties();
+ InputStream is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_FILE_MAPPING_PROPERTIES);
+ try
+ {
+ this.fileMappingProperties.load(is);
+ }
+ catch (IOException exception)
+ {
+ throw new ModuleManagementToolException("Unable to load default extension file mapping properties.", exception);
+ }
+ }
+
+ public boolean isVerbose()
+ {
+ return verbose;
+ }
+
+ public void setVerbose(boolean verbose)
+ {
+ this.verbose = verbose;
+ }
+
+ public void installModule(String ampFileLocation, String warFileLocation)
+ {
+ try
+ {
+ // Load the extension properties
+ File installingPropertiesFile = new File(ampFileLocation + "/module.properties", this.defaultDetector);
+ if (installingPropertiesFile.exists() == false)
+ {
+ throw new ModuleManagementToolException("Extension properties are not present in the AMP. Check that a valid module.properties file is present.");
+ }
+ Properties installingProperties = new Properties();
+ installingProperties.load(new FileInputStream(installingPropertiesFile));
+
+ // Get the intalling extension version
+ String installingVersionString = installingProperties.getProperty(PROP_VERSION);
+ if (installingVersionString == null || installingVersionString.length() == 0)
+ {
+ throw new ModuleManagementToolException("The version number has not been specified in the module properties found in the AMP.");
+ }
+ VersionNumber installingVersion = new VersionNumber(installingVersionString);
+
+ // Get the installed directory
+ File installDir = getInstalledDir(warFileLocation);
+
+ // Look for a previously installed version of this extension
+ File installedExtensionPropertiesFile = new File(installDir.getPath() + "/" + getModuleDetailsFileName(installingProperties.getProperty(PROP_ID)), this.defaultDetector);
+ if (installedExtensionPropertiesFile.exists() == true)
+ {
+ Properties installedExtensionProperties = new Properties();
+ InputStream is = new FileInputStream(installedExtensionPropertiesFile);
+ installedExtensionProperties.load(is);
+
+ // Get the installed version
+ VersionNumber installedVersion = new VersionNumber(installedExtensionProperties.getProperty(PROP_VERSION));
+ int compareValue = installedVersion.compareTo(installingVersion);
+ if (compareValue == -1)
+ {
+ // Trying to update the extension, old files need to cleaned before we proceed
+ cleanWAR(warFileLocation, installedExtensionProperties);
+ }
+ else if (compareValue == 0)
+ {
+ // Trying to install the same extension version again
+ verboseMessage("WARNING: This version of this module is already installed in the WAR");
+ throw new ModuleManagementToolException("This version of this module is alreay installed. Use the 'force' parameter if you want to overwrite the current installation.");
+ }
+ else if (compareValue == 1)
+ {
+ // Trying to install an earlier version of the extension
+ verboseMessage("WARNING: A later version of this module is already installed in the WAR");
+ throw new ModuleManagementToolException("An earlier version of this module is already installed. You must first unistall the current version before installing this version of the module.");
+ }
+
+ }
+
+ // TODO check for any additional file mapping propeties supplied in the AEP file
+
+ // Copy the files from the AEP file into the WAR file
+ Map modifications = new HashMap(50);
+ for (Map.Entry