package org.jboss.as.patching.installation;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jboss.as.patching.Constants;
import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.logging.PatchLogger;
import org.jboss.as.version.ProductConfig;
/**
* The installation manager.
*
* @author Emanuel Muckenhuber
*/
public class InstallationManagerImpl extends InstallationManager {
private final InstalledImage installedImage;
private InstalledIdentity defaultIdentity;
private List<File> moduleRoots;
private List<File> bundleRoots;
/**
* This field is set to true when a patch is applied/rolled back at runtime.
* It prevents another patch to be applied and overrides the modifications brought by the previous one
* unless the process is restarted first
*
* This field has to be {@code static} in order to survive server reloads.
*/
private static final AtomicBoolean restartRequired = new AtomicBoolean(false);
public InstallationManagerImpl(InstalledImage installedImage, final List<File> moduleRoots, final List<File> bundlesRoots, final ProductConfig productConfig)
throws IOException {
this.installedImage = installedImage;
this.moduleRoots = moduleRoots;
this.bundleRoots = bundlesRoots;
defaultIdentity = LayersFactory.load(installedImage, productConfig, moduleRoots, bundleRoots);
}
/**
* This method returns the installed identity with the requested name and version.
* If the product name is null, the default identity will be returned.
*
* If the product name was recognized and the requested version was not null,
* the version comparison will take place. If the version of the currently installed product
* doesn't match the requested one, the exception will be thrown.
* If the requested version is null, the currently installed identity with the requested name
* will be returned.
*
* If the product name was not recognized among the registered ones, a new installed identity
* with the requested name will be created and returned. (This is because the patching system
* is not aware of how many and what the patching streams there are expected).
*
* @param productName
* @param productVersion
* @return
* @throws PatchingException
*/
@Override
public InstalledIdentity getInstalledIdentity(String productName, String productVersion) throws PatchingException {
final String defaultIdentityName = defaultIdentity.getIdentity().getName();
if(productName == null) {
productName = defaultIdentityName;
}
final File productConf = new File(installedImage.getInstallationMetadata(), productName + Constants.DOT_CONF);
final String recordedProductVersion;
if(!productConf.exists()) {
recordedProductVersion = null;
} else {
final Properties props = loadProductConf(productConf);
recordedProductVersion = props.getProperty(Constants.CURRENT_VERSION);
}
if(defaultIdentityName.equals(productName)) {
if(recordedProductVersion != null && !recordedProductVersion.equals(defaultIdentity.getIdentity().getVersion())) {
// this means the patching history indicates that the current version is different from the one specified in the server's version module,
// which could happen in case:
// - the last applied CP didn't include the new version module or
// - the version module version included in the last CP didn't match the version specified in the CP's metadata, or
// - the version module was updated from a one-off, or
// - the patching history was edited somehow
// In any case, here I decided to rely on the patching history.
defaultIdentity = loadIdentity(productName, recordedProductVersion);
}
if(productVersion != null && !defaultIdentity.getIdentity().getVersion().equals(productVersion)) {
throw new PatchingException(PatchLogger.ROOT_LOGGER.productVersionDidNotMatchInstalled(
productName, productVersion, defaultIdentity.getIdentity().getVersion()));
}
return defaultIdentity;
}
if(recordedProductVersion != null && !Constants.UNKNOWN.equals(recordedProductVersion)) {
if(productVersion != null) {
if (!productVersion.equals(recordedProductVersion)) {
throw new PatchingException(PatchLogger.ROOT_LOGGER.productVersionDidNotMatchInstalled(productName, productVersion, recordedProductVersion));
}
} else {
productVersion = recordedProductVersion;
}
}
return loadIdentity(productName, productVersion);
}
private InstalledIdentity loadIdentity(String productName, String productVersion) throws PatchingException {
try {
return LayersFactory.load(installedImage,
new ProductConfig(productName, productVersion == null ? Constants.UNKNOWN : productVersion, null),
moduleRoots, bundleRoots);
} catch (IOException e) {
throw new PatchingException(PatchLogger.ROOT_LOGGER.failedToLoadInfo(productName), e);
}
}
private Properties loadProductConf(File productConf) throws PatchingException {
final Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(productConf)){
props.load(fis);
} catch(IOException e) {
throw new PatchingException(PatchLogger.ROOT_LOGGER.failedToLoadInfo(productConf.getAbsolutePath()), e);
}
return props;
}
@Override
public InstalledIdentity getDefaultIdentity() {
return defaultIdentity;
}
/**
* This method will return a list of installed identities for which
* the corresponding .conf file exists under .installation directory.
* The list will also include the default identity even if the .conf
* file has not been created for it.
*/
@Override
public List<InstalledIdentity> getInstalledIdentities() throws PatchingException {
List<InstalledIdentity> installedIdentities;
final File metadataDir = installedImage.getInstallationMetadata();
if(!metadataDir.exists()) {
installedIdentities = Collections.singletonList(defaultIdentity);
} else {
final String defaultConf = defaultIdentity.getIdentity().getName() + Constants.DOT_CONF;
final File[] identityConfs = metadataDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile() &&
pathname.getName().endsWith(Constants.DOT_CONF) &&
!pathname.getName().equals(defaultConf);
}
});
if(identityConfs == null || identityConfs.length == 0) {
installedIdentities = Collections.singletonList(defaultIdentity);
} else {
installedIdentities = new ArrayList<InstalledIdentity>(identityConfs.length + 1);
installedIdentities.add(defaultIdentity);
for(File conf : identityConfs) {
final Properties props = loadProductConf(conf);
String productName = conf.getName();
productName = productName.substring(0, productName.length() - Constants.DOT_CONF.length());
final String productVersion = props.getProperty(Constants.CURRENT_VERSION);
InstalledIdentity identity;
try {
identity = LayersFactory.load(installedImage, new ProductConfig(productName, productVersion, null), moduleRoots, bundleRoots);
} catch (IOException e) {
throw new PatchingException(PatchLogger.ROOT_LOGGER.failedToLoadInfo(productName), e);
}
installedIdentities.add(identity);
}
}
}
return installedIdentities;
}
@Override
public InstalledImage getInstalledImage() {
return installedImage;
}
public boolean requiresRestart() {
return restartRequired.get();
}
public boolean restartRequired() {
return restartRequired.compareAndSet(false, true);
}
public void clearRestartRequired() {
restartRequired.set(false);
}
}