/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.as.patching.installation;
import static org.jboss.as.patching.Constants.ADD_ONS;
import static org.jboss.as.patching.Constants.LAYERS;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.jboss.as.patching.Constants;
import org.jboss.as.patching.DirectoryStructure;
import org.jboss.as.patching.logging.PatchLogger;
import org.jboss.as.patching.runner.PatchUtils;
import org.jboss.as.version.ProductConfig;
/**
* @author Emanuel Muckenhuber
*/
class LayersFactory {
/**
* Load the available layers.
*
* @param image the installed image
* @param productConfig the product config to establish the identity
* @param moduleRoots the module roots
* @param bundleRoots the bundle roots
* @return the layers
* @throws IOException
*/
static InstalledIdentity load(final InstalledImage image, final ProductConfig productConfig, final List<File> moduleRoots, final List<File> bundleRoots) throws IOException {
// build the identity information
final String productVersion = productConfig.resolveVersion();
final String productName = productConfig.resolveName();
final Identity identity = new AbstractLazyIdentity() {
@Override
public String getName() {
return productName;
}
@Override
public String getVersion() {
return productVersion;
}
@Override
public InstalledImage getInstalledImage() {
return image;
}
};
final Properties properties = PatchUtils.loadProperties(identity.getDirectoryStructure().getInstallationInfo());
final List<String> allPatches = PatchUtils.readRefs(properties, Constants.ALL_PATCHES);
// Step 1 - gather the installed layers data
final InstalledConfiguration conf = createInstalledConfig(image);
// Step 2 - process the actual module and bundle roots
final ProcessedLayers processedLayers = process(conf, moduleRoots, bundleRoots);
final InstalledConfiguration config = processedLayers.getConf();
// Step 3 - create the actual config objects
// Process layers
final InstalledIdentityImpl installedIdentity = new InstalledIdentityImpl(identity, allPatches, image);
for (final LayerPathConfig layer : processedLayers.getLayers().values()) {
final String name = layer.name;
installedIdentity.putLayer(name, createPatchableTarget(name, layer, config.getLayerMetadataDir(name), image));
}
// Process add-ons
for (final LayerPathConfig addOn : processedLayers.getAddOns().values()) {
final String name = addOn.name;
installedIdentity.putAddOn(name, createPatchableTarget(name, addOn, config.getAddOnMetadataDir(name), image));
}
return installedIdentity;
}
/**
* Process the module and bundle roots and cross check with the installed information.
*
* @param conf the installed configuration
* @param moduleRoots the module roots
* @param bundleRoots the bundle roots
* @return the processed layers
* @throws IOException
*/
static ProcessedLayers process(final InstalledConfiguration conf, final List<File> moduleRoots, final List<File> bundleRoots) throws IOException {
final ProcessedLayers layers = new ProcessedLayers(conf);
// Process module roots
final LayerPathSetter moduleSetter = new LayerPathSetter() {
@Override
public boolean setPath(final LayerPathConfig pending, final File root) {
if (pending.modulePath == null) {
pending.modulePath = root;
return true;
}
return false;
}
};
for (final File moduleRoot : moduleRoots) {
processRoot(moduleRoot, layers, moduleSetter);
}
// Process bundle root
final LayerPathSetter bundleSetter = new LayerPathSetter() {
@Override
public boolean setPath(LayerPathConfig pending, File root) {
if (pending.bundlePath == null) {
pending.bundlePath = root;
return true;
}
return false;
}
};
for (final File bundleRoot : bundleRoots) {
processRoot(bundleRoot, layers, bundleSetter);
}
// if (conf.getInstalledLayers().size() != layers.getLayers().size()) {
// throw processingError("processed layers don't match expected %s, but was %s", conf.getInstalledLayers(), layers.getLayers().keySet());
// }
// if (conf.getInstalledAddOns().size() != layers.getAddOns().size()) {
// throw processingError("processed add-ons don't match expected %s, but was %s", conf.getInstalledAddOns(), layers.getAddOns().keySet());
// }
return layers;
}
/**
* Process a module or bundle root.
*
* @param root the root
* @param layers the processed layers
* @param setter the bundle or module path setter
* @throws IOException
*/
static void processRoot(final File root, final ProcessedLayers layers, final LayerPathSetter setter) throws IOException {
final LayersConfig layersConfig = LayersConfig.getLayersConfig(root);
// Process layers
final File layersDir = new File(root, layersConfig.getLayersPath());
if (!layersDir.exists()) {
if (layersConfig.isConfigured()) {
// Bad config from user
throw PatchLogger.ROOT_LOGGER.installationNoLayersConfigFound(layersDir.getAbsolutePath());
}
// else this isn't a root that has layers and add-ons
} else {
// check for a valid layer configuration
for (final String layer : layersConfig.getLayers()) {
File layerDir = new File(layersDir, layer);
if (!layerDir.exists()) {
if (layersConfig.isConfigured()) {
// Bad config from user
throw PatchLogger.ROOT_LOGGER.installationMissingLayer(layer, layersDir.getAbsolutePath());
}
// else this isn't a standard layers and add-ons structure
return;
}
layers.addLayer(layer, layerDir, setter);
}
}
// Finally process the add-ons
final File addOnsDir = new File(root, layersConfig.getAddOnsPath());
final File[] addOnsList = addOnsDir.listFiles();
if (addOnsList != null) {
for (final File addOn : addOnsList) {
layers.addAddOn(addOn.getName(), addOn, setter);
}
}
}
/**
* Create the actual patchable target.
*
* @param name the layer name
* @param layer the layer path config
* @param metadata the metadata location for this target
* @param image the installed image
* @return the patchable target
* @throws IOException
*/
static AbstractLazyPatchableTarget createPatchableTarget(final String name, final LayerPathConfig layer, final File metadata, final InstalledImage image) throws IOException {
// patchable target
return new AbstractLazyPatchableTarget() {
@Override
public InstalledImage getInstalledImage() {
return image;
}
@Override
public File getModuleRoot() {
return layer.modulePath;
}
@Override
public File getBundleRepositoryRoot() {
return layer.bundlePath;
}
public File getPatchesMetadata() {
return metadata;
}
@Override
public String getName() {
return name;
}
};
}
static class LayerPathConfig {
File modulePath;
File bundlePath;
final String name;
LayerPathConfig(String name) {
this.name = name;
}
}
interface LayerPathSetter {
/**
* Set the path for the layer.
*
* @param pending the pending layer
* @param root the root
* @return {@code true} if the root wasn't set, {@code false} otherwise
*/
boolean setPath(LayerPathConfig pending, File root);
}
/**
* Resolve the installed layers and add-ons.
*
* @param image the installed image
* @return the installed layers
*/
static InstalledConfiguration createInstalledConfig(final InstalledImage image) {
final InstalledConfiguration conf = new InstalledConfiguration(image);
// Would be nice to have an installed inventory or smth like that
return conf;
}
static class ProcessedLayers {
private final InstalledConfiguration conf;
private final Map<String, LayerPathConfig> layers = new LinkedHashMap<String, LayerPathConfig>();
private final Map<String, LayerPathConfig> addOns = new LinkedHashMap<String, LayerPathConfig>();
ProcessedLayers(InstalledConfiguration conf) {
this.conf = conf;
}
InstalledConfiguration getConf() {
return conf;
}
Map<String, LayerPathConfig> getLayers() {
return layers;
}
Map<String, LayerPathConfig> getAddOns() {
return addOns;
}
void addLayer(final String name, final File root, final LayerPathSetter setter) {
// if (!conf.getInstalledLayers().contains(name)) {
// throw processingError("layer '%s' not configured ", name);
// }
LayerPathConfig pending = layers.get(name);
if (pending == null) {
pending = new LayerPathConfig(name);
layers.put(name, pending);
}
if (!setter.setPath(pending, root)) {
// Already set means duplicate
throw PatchLogger.ROOT_LOGGER.installationDuplicateLayer("layer", name);
}
}
void addAddOn(final String name, final File root, LayerPathSetter setter) {
// if (!conf.getInstalledAddOns().contains(name)) {
// throw processingError("add-on '%s' not configured ", name);
// }
LayerPathConfig pending = addOns.get(name);
if (pending == null) {
pending = new LayerPathConfig(name);
addOns.put(name, pending);
}
if (!setter.setPath(pending, root)) {
// Already set means duplicate
throw PatchLogger.ROOT_LOGGER.installationDuplicateLayer("add-on", name);
}
}
}
static class InstalledConfiguration {
final File metadata;
final InstalledImage installedImage;
final Set<String> installedLayers = new HashSet<String>();
final Set<String> installedAddOns = new HashSet<String>();
InstalledConfiguration(final InstalledImage installedImage) {
this.metadata = installedImage.getInstallationMetadata();
this.installedImage = installedImage;
}
File getLayersMetadataDir() {
return new File(metadata, LAYERS);
}
File getLayerMetadataDir(final String name) {
return new File(getLayersMetadataDir(), name);
}
File getAddOnsMetadataDir() {
return new File(metadata, ADD_ONS);
}
File getAddOnMetadataDir(final String name) {
return new File(getAddOnsMetadataDir(), name);
}
Set<String> getInstalledLayers() {
return installedLayers;
}
Set<String> getInstalledAddOns() {
return installedAddOns;
}
}
/**
* @author Emanuel Muckenhuber
*/
abstract static class AbstractLazyPatchableTarget extends LayerDirectoryStructure implements Layer, AddOn {
@Override
File getPatchesMetadata() {
return getPatchesMetadata(getName());
}
@Override
public DirectoryStructure getDirectoryStructure() {
return this;
}
@Override
public TargetInfo loadTargetInfo() throws IOException {
return LayerInfo.loadTargetInfoFromDisk(getDirectoryStructure());
}
}
/**
* @author Emanuel Muckenhuber
*/
abstract static class AbstractLazyIdentity extends LayerDirectoryStructure.IdentityDirectoryStructure implements Identity {
@Override
public DirectoryStructure getDirectoryStructure() {
return this;
}
@Override
public TargetInfo loadTargetInfo() throws IOException {
return LayerInfo.loadTargetInfoFromDisk(getDirectoryStructure());
}
@Override
public File getInstallationInfo() {
return new File(getPatchesMetadata(), getName() + Constants.DOT_CONF);
}
}
}