/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb3.packagemanager.impl;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Set;
import javax.transaction.TransactionManager;
import org.jboss.ejb3.packagemanager.PackageContext;
import org.jboss.ejb3.packagemanager.PackageManager;
import org.jboss.ejb3.packagemanager.PackageManagerContext;
import org.jboss.ejb3.packagemanager.PackageManagerEnvironment;
import org.jboss.ejb3.packagemanager.PackageVersionComparator;
import org.jboss.ejb3.packagemanager.db.DefaultDatabaseManager;
import org.jboss.ejb3.packagemanager.db.PackageDatabaseManager;
import org.jboss.ejb3.packagemanager.entity.PersistentFile;
import org.jboss.ejb3.packagemanager.entity.PersistentPackage;
import org.jboss.ejb3.packagemanager.exception.PackageManagerException;
import org.jboss.ejb3.packagemanager.exception.PackageNotInstalledException;
import org.jboss.ejb3.packagemanager.installer.DefaultInstaller;
import org.jboss.ejb3.packagemanager.installer.Installer;
import org.jboss.ejb3.packagemanager.installer.MergingInstaller;
import org.jboss.ejb3.packagemanager.metadata.FileType;
import org.jboss.ejb3.packagemanager.metadata.InstallFileType;
import org.jboss.ejb3.packagemanager.metadata.PackagedDependency;
import org.jboss.ejb3.packagemanager.metadata.ScriptType;
import org.jboss.ejb3.packagemanager.metadata.UnProcessedDependenciesType;
import org.jboss.ejb3.packagemanager.option.DefaultInstallOptions;
import org.jboss.ejb3.packagemanager.option.DefaultUnInstallOptions;
import org.jboss.ejb3.packagemanager.option.DefaultUpgradeOptions;
import org.jboss.ejb3.packagemanager.option.InstallOptions;
import org.jboss.ejb3.packagemanager.option.UnInstallOptions;
import org.jboss.ejb3.packagemanager.option.UpgradeOptions;
import org.jboss.ejb3.packagemanager.script.ScriptProcessor;
import org.jboss.ejb3.packagemanager.script.impl.AntScriptProcessor;
import org.jboss.ejb3.packagemanager.util.IOUtil;
import org.jboss.logging.Logger;
/**
* DefaultPackageManagerImpl
*
* @author Jaikiran Pai
* @version $Revision: $
*/
public class DefaultPackageManagerImpl implements PackageManager
{
/**
* Logger
*/
private static Logger logger = Logger.getLogger(DefaultPackageManagerImpl.class);
/**
* The server against which this package manager will carry out the installation/uninstallation
*
*/
private String installationServerHome;
/**
* Package manager environment
*/
private PackageManagerEnvironment environment;
/**
* Package manager context
*/
private PackageManagerContext pkgMgrCtx;
/**
* Package database manager
*/
private PackageDatabaseManager pkgDatabaseManager;
/**
* Creates the default package manager for a server
*
* @param environment The package manager environment
* @param jbossHome The JBoss AS server home
*/
public DefaultPackageManagerImpl(PackageManagerEnvironment environment, String jbossHome)
{
this.environment = environment;
this.installationServerHome = jbossHome;
this.pkgMgrCtx = new DefaultPackageManagerContext(this);
this.pkgDatabaseManager = new DefaultDatabaseManager(this.pkgMgrCtx);
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#getPackageManagerEnvironment()
*/
public PackageManagerEnvironment getPackageManagerEnvironment()
{
return this.environment;
}
/**
* Returns the server home against which this package manager operates
*/
public String getServerHome()
{
return this.installationServerHome;
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#getDatabaseManager()
*/
@Override
public PackageDatabaseManager getDatabaseManager()
{
return this.pkgDatabaseManager;
}
/**
* Installs a package
*
* @param pkgPath The URL to the package that is to be installed
* @throws PackageManagerException If any exceptions occur during installation of the package
* @see org.jboss.ejb3.packagemanager.PackageManager#installPackage(java.lang.String)
*/
@Override
public void installPackage(String pkgPath) throws PackageManagerException
{
this.installPackage(pkgPath, new DefaultInstallOptions());
}
/**
* Installs the package from the {@code packageURL}
*
* @param packageURL The URL to the package that is to be installed
* @throws PackageManagerException If any exceptions occur during installation of the package
* @see org.jboss.ejb3.packagemanager.PackageManager#installPackage(URL)
*
*/
@Override
public void installPackage(URL packageURL) throws PackageManagerException
{
this.installPackage(packageURL, new DefaultInstallOptions());
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#installPackage(java.lang.String, org.jboss.ejb3.packagemanager.option.InstallOptions)
*/
@Override
public void installPackage(String pkgPath, InstallOptions installOptions) throws PackageManagerException
{
if (pkgPath == null)
{
throw new PackageManagerException("Package path is null");
}
URL packageURL = null;
try
{
packageURL = this.getPackageURL(pkgPath);
}
catch (MalformedURLException mue)
{
throw new PackageManagerException("Cannot parse path " + pkgPath, mue);
}
this.installPackage(packageURL, installOptions);
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#installPackage(java.net.URL, org.jboss.ejb3.packagemanager.option.InstallOptions)
*/
@Override
public void installPackage(URL packageURL, InstallOptions installOptions) throws PackageManagerException
{
if (packageURL == null)
{
throw new PackageManagerException("Package URL is null");
}
// create a package context
PackageContext pkgCtx = new DefaultPackageContext(this.pkgMgrCtx, packageURL);
this.installPackage(pkgCtx, installOptions);
}
/**
*
* @param pkgContext
* @throws PackageManagerException
*/
protected void installPackage(PackageContext pkgContext, InstallOptions installOptions) throws PackageManagerException
{
if (pkgContext == null)
{
throw new PackageManagerException("Package context is null");
}
// check if package is already installed
boolean packageAlreadyInstalled = this.pkgDatabaseManager.isPackageInstalled(pkgContext.getPackageName());
if (packageAlreadyInstalled)
{
throw new PackageManagerException("Package " + pkgContext + " is already installed");
}
logger.debug("New package " + pkgContext + " being installed");
// proceed with installation of the package
if (pkgContext.getInstallationFiles() == null)
{
throw new PackageManagerException("There are no files to install for package: " + pkgContext);
}
// work on dependencies first (because if deps are not satisfied then no point
// running the pre-install step.
// BUT, think about this (should we first run pre-install and then deps?).
// What would be a ideal behaviour?
this.installDependencies(pkgContext);
// pre-installation step
this.preInstallPackage(pkgContext);
// install files in this package
for (InstallFileType fileToInstall : pkgContext.getInstallationFiles())
{
Installer installer = getInstaller(fileToInstall);
installer.install(pkgContext, fileToInstall);
}
// post-installation step
this.postInstallPackage(pkgContext);
// store the pre/post uninstall scripts (if any) at a particular location
// so that they can be used during uninstallation of this package
this.storeScripts(pkgContext);
// now record the installation into DB
this.pkgDatabaseManager.installPackage(pkgContext);
logger.info("Installed " + pkgContext);
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#removePackage(java.lang.String)
*/
@Override
public void removePackage(String packageName) throws PackageNotInstalledException, PackageManagerException
{
this.removePackage(packageName, new DefaultUnInstallOptions());
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#removePackage(java.lang.String, org.jboss.ejb3.packagemanager.option.UnInstallOptions)
*/
@Override
public void removePackage(String packageName, UnInstallOptions uninstallOptions)
throws PackageNotInstalledException, PackageManagerException
{
// get the installed package
boolean isPackageInstalled = this.pkgDatabaseManager.isPackageInstalled(packageName);
if (!isPackageInstalled)
{
throw new PackageNotInstalledException("Package " + packageName + " is not installed - so cannot be removed!");
}
PersistentPackage installedPackage = this.pkgDatabaseManager.getInstalledPackage(packageName);
if (!uninstallOptions.isForceUnInstall())
{
// check if other packages are dependent on this package
// If yes, then do NOT remove this package. Else remove this package
Set<PersistentPackage> dependentPackages = this.pkgDatabaseManager.getDependentPackages(packageName);
if (dependentPackages != null && !dependentPackages.isEmpty())
{
throw new PackageManagerException("Other packages are dependent on package " + packageName
+ " - cannot remove this package!");
}
}
// pre-uninstall step
this.preUnInstallPackage(installedPackage);
// TODO : Revisit this installer creation
Installer installer = new DefaultInstaller(this.pkgMgrCtx);
// install files in this package
for (PersistentFile fileToUninstall : installedPackage.getInstallationFiles())
{
installer.uninstall(installedPackage, fileToUninstall);
}
// post-uninstall step
this.postUnInstallPackage(installedPackage);
this.pkgDatabaseManager.removePackage(installedPackage);
logger.info("Uninstalled " + packageName);
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#updatePackage(java.lang.String)
*/
@Override
public void updatePackage(String packageFilePath) throws PackageManagerException
{
this.updatePackage(packageFilePath, new DefaultUpgradeOptions());
}
@Override
public void updatePackage(URL packageURL) throws PackageManagerException
{
this.updatePackage(packageURL, new DefaultUpgradeOptions());
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#updatePackage(java.lang.String, org.jboss.ejb3.packagemanager.option.UpgradeOptions)
*/
@Override
public void updatePackage(String packageFilePath, UpgradeOptions upgradeOptions) throws PackageManagerException
{
if (packageFilePath == null)
{
throw new PackageManagerException("Package path is null");
}
URL packageURL = null;
try
{
packageURL = this.getPackageURL(packageFilePath);
}
catch (MalformedURLException mue)
{
throw new PackageManagerException("Cannot parse path " + packageFilePath, mue);
}
this.updatePackage(packageURL, upgradeOptions);
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#updatePackage(java.net.URL, org.jboss.ejb3.packagemanager.option.UpgradeOptions)
*/
@Override
public void updatePackage(URL packageURL, UpgradeOptions upgradeOptions) throws PackageManagerException
{
if (packageURL == null)
{
throw new PackageManagerException("Package URL is null");
}
// create a package context
PackageContext pkgCtx = new DefaultPackageContext(this.pkgMgrCtx, packageURL);
this.updatePackage(pkgCtx, upgradeOptions);
}
protected void updatePackage(PackageContext pkgContext, UpgradeOptions upgradeOptions) throws PackageManagerException
{
String packageName = pkgContext.getPackageName();
boolean isPackageInstalled = this.pkgDatabaseManager.isPackageInstalled(packageName);
if (isPackageInstalled)
{
PersistentPackage installedPackage = this.pkgDatabaseManager.getInstalledPackage(packageName);
// if the installed package version and the version of the package being upgraded are the same
// then skip the upgrade
PackageVersionComparator comparator = new PackageVersionComparator();
int versionComparison = comparator.compare(installedPackage.getPackageVersion(), pkgContext.getPackageVersion());
if (versionComparison == 0)
{
if (!upgradeOptions.isForceUpgrade())
{
logger.info("Skipping package upgrade, since package with name " + packageName + " and version "
+ installedPackage.getPackageVersion() + " is already installed");
return;
}
}
else if (versionComparison > 0)
{
logger.info("Skipping package upgrade, since a higher version " + installedPackage.getPackageVersion()
+ " compared to " + pkgContext.getPackageVersion() + " for package " + packageName
+ " is already installed");
return;
}
// this is a newer version, so continue with upgrade
logger.info("Upgrading package " + packageName + " from installed version " + installedPackage.getPackageVersion()
+ " to new version " + pkgContext.getPackageVersion());
UnInstallOptions uninstallOptions = new DefaultUnInstallOptions();
uninstallOptions.setForcedUnInstall(true);
removePackage(packageName, uninstallOptions);
}
// now install new version
this.installPackage(pkgContext, new DefaultInstallOptions());
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#getTransactionManager()
*/
@Override
public TransactionManager getTransactionManager()
{
return null;
}
/**
* Parses the {@code pkgPath} string and returns an appropriate URL.
*
* @param pkgPath
* @return
*
* @throws MalformedURLException If the URL cannot be parsed
*/
private URL getPackageURL(String pkgPath) throws MalformedURLException
{
URL url = null;
try
{
url = new URL(pkgPath);
}
catch (MalformedURLException e)
{
logger.debug("Cannot handle " + pkgPath + " - will try using file: URL");
// fall back to file based URL
File file = new File(pkgPath);
url = file.toURI().toURL();
}
return url;
}
/**
* Returns the appropriate {@link Installer} for the {@link InstallFileType}
*/
private Installer getInstaller(InstallFileType file)
{
if (file == null)
{
return null;
}
FileType fileType = file.getType();
if (fileType == FileType.CONFIG)
{
return new MergingInstaller(this.pkgMgrCtx);
}
return new DefaultInstaller(this.pkgMgrCtx);
}
/**
* Stores the scripts available in a package to a local store so
* that they are available during uninstallation of the package.
* Currently, only post-uninstall and pre-uinstall scripts are
* stored because the rest of the scripts are not required
* after installation of a package.
*
* @param pkgCtx Package whose scripts are to be stored
*/
private void storeScripts(PackageContext pkgCtx) throws PackageManagerException
{
String relativePathToScriptStoreDir = this.pkgMgrCtx.getScriptStoreLocation(pkgCtx);
File scriptStoreDir = new File(this.getPackageManagerEnvironment().getPackageManagerHome(),
relativePathToScriptStoreDir);
if (!scriptStoreDir.exists())
{
scriptStoreDir.mkdirs();
}
logger.debug("Scripts for " + pkgCtx + " will be stored in " + scriptStoreDir);
// we store only uninstall scripts.
// post-uninstall
List<ScriptType> postUnInstallScripts = pkgCtx.getPostUnInstallScripts();
if (postUnInstallScripts != null)
{
for (ScriptType script : postUnInstallScripts)
{
storeScript(pkgCtx, script, scriptStoreDir);
}
}
// pre-uninstall
List<ScriptType> preUnInstallScripts = pkgCtx.getPreUnInstallScripts();
if (preUnInstallScripts != null)
{
for (ScriptType script : preUnInstallScripts)
{
storeScript(pkgCtx, script, scriptStoreDir);
}
}
}
/**
* Stores the script to the <code>destDir</code>
*
* @param pkgCtx Package to which this script belongs
* @param script Script to be stored
* @param destDir The destination directory where the script has to be stored
* @throws PackageManagerException
*/
private void storeScript(PackageContext pkgCtx, ScriptType script, File destDir) throws PackageManagerException
{
String scriptFileName = script.getName();
File root = pkgCtx.getPackageRoot();
File path = root;
if (script.getPath() != null)
{
path = new File(root, script.getPath());
}
File scriptFile = new File(path, scriptFileName);
if (!scriptFile.exists())
{
throw new PackageManagerException("Script file " + scriptFile + " for " + pkgCtx + " does not exist!");
}
try
{
File destFile = new File(destDir, scriptFile.getName());
IOUtil.copy(scriptFile, destFile);
logger.debug("Stored script file " + scriptFile + " at " + destDir);
}
catch (IOException e)
{
throw new PackageManagerException("Could not store script due to exception ", e);
}
}
/**
* The pre-installation step for packages. Each package can have multiple
* pre-install scripts to be run. This method runs those pre-install scripts
*
* @param pkgCtx The package source
* @throws PackageManagerException If any exception occurs during pre-installation of the
* package
*/
protected void preInstallPackage(PackageContext pkgCtx) throws PackageManagerException
{
// find any pre-install scripts
List<ScriptType> preInstallScripts = pkgCtx.getPreInstallScripts();
if (preInstallScripts == null || preInstallScripts.isEmpty())
{
logger.trace("There are no pre-install scripts for " + pkgCtx);
return;
}
for (ScriptType script : preInstallScripts)
{
// TODO: Can we just have one instance of the script processor to process
// all scripts? Stateful/stateless?
ScriptProcessor scriptProcessor = new AntScriptProcessor();
String scriptFileName = script.getName();
File root = pkgCtx.getPackageRoot();
File path = root;
if (script.getPath() != null)
{
path = new File(root, script.getPath());
}
File scriptFile = new File(path, scriptFileName);
if (!scriptFile.exists())
{
throw new PackageManagerException("Script file " + scriptFile + " for " + pkgCtx + " does not exist!");
}
scriptProcessor.processPreInstallScript(this.pkgMgrCtx, pkgCtx, scriptFile);
}
}
/**
* The pre-uninstallation step for packages. Each package can have multiple
* pre-uninstall scripts to be run. This method runs those pre-uninstall scripts
*
* @param installedPackage The installed package
* @throws PackageManagerException If any exception occurs during pre-uninstallation of the
* package
*/
protected void preUnInstallPackage(PersistentPackage installedPackage) throws PackageManagerException
{
// find any pre-uninstall scripts
Set<org.jboss.ejb3.packagemanager.entity.PersistentPreUnInstallScript> preUnInstallScripts = installedPackage
.getPreUnInstallScripts();
if (preUnInstallScripts == null || preUnInstallScripts.isEmpty())
{
logger.trace("There are no pre-uninstall scripts for package " + installedPackage.getPackageName());
return;
}
for (org.jboss.ejb3.packagemanager.entity.PersistentPreUnInstallScript script : preUnInstallScripts)
{
ScriptProcessor scriptProcessor = new AntScriptProcessor();
File packageManagerHome = this.pkgMgrCtx.getPackageManagerEnvironment().getPackageManagerHome();
File scriptFileLocation = new File(packageManagerHome, script.getPath());
File scriptFile = new File(scriptFileLocation, script.getName());
if (!scriptFile.exists())
{
throw new PackageManagerException("Script file " + scriptFile + " for package "
+ installedPackage.getPackageName() + " does not exist!");
}
scriptProcessor.processPreUnInstallScript(this.pkgMgrCtx, installedPackage, scriptFile);
}
}
/**
* The post-installation step for packages. Each package can have multiple
* post-install scripts to be run. This method runs those post-install scripts
*
* @param pkgCtx The package source
* @throws PackageManagerException If any exception occurs during post-installation of the
* package
*/
protected void postInstallPackage(PackageContext pkgCtx) throws PackageManagerException
{
// find any post-install scripts
List<ScriptType> postInstallScripts = pkgCtx.getPostInstallScripts();
if (postInstallScripts == null || postInstallScripts.isEmpty())
{
logger.trace("There are no post-install scripts for " + pkgCtx);
return;
}
for (ScriptType script : postInstallScripts)
{
ScriptProcessor scriptProcessor = new AntScriptProcessor();
// TODO: Can we just have one instance of the script processor to process
// all scripts? Stateful/stateless?
String scriptFileName = script.getName();
File root = pkgCtx.getPackageRoot();
File path = root;
if (script.getPath() != null)
{
path = new File(root, script.getPath());
}
File scriptFile = new File(path, scriptFileName);
if (!scriptFile.exists())
{
throw new PackageManagerException("Script file " + scriptFile + " for " + pkgCtx + " does not exist!");
}
scriptProcessor.processPostInstallScript(this.pkgMgrCtx, pkgCtx, scriptFile);
}
}
/**
* The post-uninstallation step for packages. Each package can have multiple
* post-uninstall scripts to be run. This method runs those post-uninstall scripts
*
* @param installedPackage The installed package
* @throws PackageManagerException If any exception occurs during post-uninstallation of the
* package
*/
protected void postUnInstallPackage(PersistentPackage installedPackage) throws PackageManagerException
{
// find any post-uninstall scripts
Set<org.jboss.ejb3.packagemanager.entity.PersistentPostUnInstallScript> postUnInstallScripts = installedPackage
.getPostUnInstallScripts();
if (postUnInstallScripts == null || postUnInstallScripts.isEmpty())
{
logger.trace("There are no post-uninstall scripts for package " + installedPackage.getPackageName());
return;
}
for (org.jboss.ejb3.packagemanager.entity.PersistentPostUnInstallScript script : postUnInstallScripts)
{
ScriptProcessor scriptProcessor = new AntScriptProcessor();
File packageManagerHome = this.pkgMgrCtx.getPackageManagerEnvironment().getPackageManagerHome();
File scriptFileLocation = new File(packageManagerHome, script.getPath());
File scriptFile = new File(scriptFileLocation, script.getName());
if (!scriptFile.exists())
{
throw new PackageManagerException("Script file " + scriptFile + " for package "
+ installedPackage.getPackageName() + " does not exist!");
}
scriptProcessor.processPostUnInstallScript(this.pkgMgrCtx, installedPackage, scriptFile);
}
}
/**
* Process any dependencies listed for the package. Dependency processing
* will include resolving and retrieving the appropriate dependency packages
* and install those packages. Dependency packages, either in the form of {@link PackagedDependency}
* or {@link UnProcessedDependenciesType} will be installed during this process.
*
* First the packaged dependencies are installed and then the unprocessed dependencies
*
* @param pkgContext
* @throws PackageManagerException
*/
protected void installDependencies(PackageContext pkgContext) throws PackageManagerException
{
Set<PackageContext> dependencies = pkgContext.getDependencyPackages();
for (PackageContext dependencyPackage : dependencies)
{
logger.info("Installing dependency package : " + dependencyPackage + " for dependent package: " + pkgContext);
this.updatePackage(dependencyPackage, new DefaultUpgradeOptions());
}
}
/**
* @see org.jboss.ejb3.packagemanager.PackageManager#getAllInstalledPackages()
*/
@Override
public Set<String> getAllInstalledPackages()
{
return this.pkgDatabaseManager.getAllInstalledPackages();
}
}