/*
* 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.tool;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.Properties;
import org.alfresco.service.cmr.module.ModuleInstallState;
import org.doomdark.uuid.UUIDGenerator;
import de.schlichtherle.io.DefaultRaesZipDetector;
import de.schlichtherle.io.File;
import de.schlichtherle.io.FileInputStream;
import de.schlichtherle.io.ZipControllerException;
import de.schlichtherle.io.ZipDetector;
import de.schlichtherle.io.ZipWarningException;
/**
* Module management tool.
*
* Manages the modules installed in a war file. Allows modules to be installed, updated, enabled, disabled and
* uninstalled. Information about the module installed is also available.
*
* @author Roy Wetherall
*/
public class ModuleManagementTool
{
/** Location of the default mapping properties file */
private static final String DEFAULT_FILE_MAPPING_PROPERTIES = "org/alfresco/repo/module/tool/default-file-mapping.properties";
/** Location of the AMP-specific mappings file */
private static final String FILE_MAPPING_PROPERTIES = "file-mapping.properties";
/**
* The property to add to a custom {@link #FILE_MAPPING_PROPERTIES file-mapping.properties} to inherit the default values.
* The default is true.
*/
private static final String PROP_INHERIT_DEFAULT = "inherit.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";
/** Operations and options supperted via the command line interface to this class */
private static final String OP_INSTALL = "install";
private static final String OP_LIST = "list";
private static final String OPTION_VERBOSE = "-verbose";
private static final String OPTION_FORCE = "-force";
private static final String OPTION_PREVIEW = "-preview";
private static final String OPTION_NOBACKUP = "-nobackup";
private static final String OPTION_DIRECTORY = "-directory";
/** Default zip detector */
public static final ZipDetector DETECTOR_AMP_AND_WAR = new DefaultRaesZipDetector("amp|war");
/** File mapping properties */
private Properties defaultFileMappingProperties;
/** Indicates the current verbose setting */
private boolean verbose = false;
/**
* Constructor
*/
public ModuleManagementTool()
{
// Load the default file mapping properties
this.defaultFileMappingProperties = new Properties();
InputStream is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_FILE_MAPPING_PROPERTIES);
try
{
this.defaultFileMappingProperties.load(is);
}
catch (IOException exception)
{
throw new ModuleManagementToolException("Unable to load default extension file mapping properties.", exception);
}
}
/**
* Indicates whether the management tool is currently in verbose reporting mode.
*
* @return true if verbose, false otherwise
*/
public boolean isVerbose()
{
return verbose;
}
/**
* Sets the verbose setting for the mangement tool
*
* @param verbose true if verbose, false otherwise
*/
public void setVerbose(boolean verbose)
{
this.verbose = verbose;
}
/**
*
* @param directory
* @param warFileLocation
*/
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);
if (dir.exists() == true)
{
installModules(dir, warFileLocation, preview, forceInstall,backupWAR);
}
else
{
throw new ModuleManagementToolException("Invalid directory '" + directoryLocation + "'");
}
}
/**
*
* @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();
if (children != null)
{
for (java.io.File child : children)
{
if (child.isFile() == true && child.getName().toLowerCase().endsWith(".amp") == true)
{
installModule(child.getPath(), warFileLocation, preview, forceInstall, backupWAR);
}
else
{
installModules(child, warFileLocation, preview, forceInstall, backupWAR);
}
}
}
}
/**
* Installs a given AMP file into a given WAR file.
*
* @see ModuleManagementTool.installModule(String, String, boolean, boolean, boolean)
*
* @param ampFileLocation the location of the AMP file to be installed
* @param warFileLocation the location of the WAR file into which the AMP file is to be installed
*/
public void installModule(String ampFileLocation, String warFileLocation)
{
installModule(ampFileLocation, warFileLocation, false, false, true);
}
/**
* Installs a given AMP file into a given WAR file.
*
* @param ampFileLocation the location of the AMP file to be installed
* @param warFileLocation the location of the WAR file into which the AMP file is to be installed.
* @param preview indicates whether this should be a preview install. This means that the process of
* installation will be followed and reported, but the WAR file will not be modified.
* @param forceInstall indicates whether the installed files will be replaces reguarless of the currently installed
* version of the AMP. Generally used during development of the AMP.
* @param backupWAR indicates whether we should backup the war we are modifying or not
*/
public void installModule(String ampFileLocation, String warFileLocation, boolean preview, boolean forceInstall, boolean backupWAR)
{
try
{
outputMessage("Installing AMP '" + ampFileLocation + "' into WAR '" + warFileLocation + "'");
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);
if (moduleDir.exists() == false)
{
moduleDir.mkdir();
}
File backUpDir = new File(warFileLocation + BACKUP_DIR, DETECTOR_AMP_AND_WAR);
if (backUpDir.exists() == false)
{
backUpDir.mkdir();
}
// Make a backup of the war we are oging to modify
if (backupWAR == true)
{
java.io.File warFile = new java.io.File(warFileLocation);
if (warFile.exists() == false)
{
throw new ModuleManagementToolException("The war file '" + warFileLocation + "' does not exist.");
}
String backupLocation = warFileLocation + "-" + System.currentTimeMillis() + ".bak";
java.io.File backup = new java.io.File(backupLocation);
copyFile(warFile, backup);
outputMessage("WAR has been backed up to '" + backupLocation + "'");
}
}
// Get the details of the installing module
ModuleDetailsHelper installingModuleDetails = ModuleDetailsHelper.create(ampFileLocation + "/module.properties");
if (installingModuleDetails.exists() == false)
{
throw new ModuleManagementToolException("No module.properties file has been found in the installing .amp file '" + ampFileLocation + "'");
}
// Get the detail of the installed module
ModuleDetailsHelper installedModuleDetails = ModuleDetailsHelper.create(warFileLocation, installingModuleDetails.getId());
if (installedModuleDetails != null)
{
int compareValue = installedModuleDetails.getVersionNumber().compareTo(installingModuleDetails.getVersionNumber());
if (forceInstall == true || compareValue == -1)
{
if (forceInstall == true)
{
// Warn of forced install
outputMessage("WARNING: The installation of this module is being forced. All files will be removed and replaced regardless of exiting versions present.");
}
// Trying to update the extension, old files need to cleaned before we proceed
outputMessage("Clearing out files relating to version '" + installedModuleDetails.getVersionNumber() + "' of module '" + installedModuleDetails.getId() + "'");
cleanWAR(warFileLocation, installedModuleDetails.getId(), preview);
}
else if (compareValue == 0)
{
// Trying to install the same extension version again
outputMessage("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
outputMessage("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.");
}
}
// Check if a custom mapping file has been defined
Properties fileMappingProperties = null;
Properties customFileMappingProperties = getCustomFileMappings(ampFileLocation);
if (customFileMappingProperties == null)
{
fileMappingProperties = defaultFileMappingProperties;
}
else
{
fileMappingProperties = new Properties();
// A custom mapping file was present. Check if it must inherit the default mappings.
String inheritDefaultStr = customFileMappingProperties.getProperty(PROP_INHERIT_DEFAULT, "true");
if (inheritDefaultStr.equalsIgnoreCase("true"))
{
fileMappingProperties.putAll(defaultFileMappingProperties);
}
fileMappingProperties.putAll(customFileMappingProperties);
fileMappingProperties.remove(PROP_INHERIT_DEFAULT);
}
// Copy the files from the AEP file into the WAR file
outputMessage("Adding files relating to version '" + installingModuleDetails.getVersionNumber() + "' of module '" + installingModuleDetails.getId() + "'");
InstalledFiles installedFiles = new InstalledFiles(warFileLocation, installingModuleDetails.getId());
for (Map.Entry