blob: 6160c1389f80e25be8a40b7aa1bda5d03a63b301 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.apache.karaf.profile.assembly;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.apache.felix.utils.properties.Properties;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.DeploymentEvent;
import org.apache.karaf.features.FeatureEvent;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.internal.download.DownloadManager;
import org.apache.karaf.features.internal.download.Downloader;
import org.apache.karaf.features.internal.model.Config;
import org.apache.karaf.features.internal.model.ConfigFile;
import org.apache.karaf.features.internal.model.Feature;
import org.apache.karaf.features.internal.model.Features;
import org.apache.karaf.features.internal.model.Library;
import org.apache.karaf.features.internal.service.Deployer;
import org.apache.karaf.features.internal.service.FeaturesProcessor;
import org.apache.karaf.features.internal.service.State;
import org.apache.karaf.features.internal.service.StaticInstallSupport;
import org.apache.karaf.features.internal.util.MapUtils;
import org.apache.karaf.util.maven.Parser;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.wiring.BundleRevision;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback through which {@link Deployer} will interact with the distribution that's being assembled.
*/
public class AssemblyDeployCallback extends StaticInstallSupport implements Deployer.DeployCallback {
private static final Logger LOGGER = LoggerFactory.getLogger(Builder.class);
private final DownloadManager manager;
private final Builder builder;
private final Path homeDirectory;
private final int defaultStartLevel;
private final Path etcDirectory;
private final Path systemDirectory;
private final Deployer.DeploymentState dstate;
private final AtomicLong nextBundleId = new AtomicLong(0);
private final FeaturesProcessor processor;
private final Map<String, Bundle> bundles = new HashMap<>();
/**
* Create a {@link Deployer.DeployCallback} performing actions on runtime with single system bundle installed
* and with access to all non-blacklisted features.
* @param manager
* @param builder
* @param systemBundle
* @param repositories
* @param processor
*/
public AssemblyDeployCallback(DownloadManager manager, Builder builder, BundleRevision systemBundle, Collection<Features> repositories,
FeaturesProcessor processor) {
this.manager = manager;
this.builder = builder;
this.homeDirectory = builder.homeDirectory;
this.etcDirectory = homeDirectory.resolve("etc");
this.systemDirectory = homeDirectory.resolve("system");
this.defaultStartLevel = builder.defaultStartLevel;
this.processor = processor;
dstate = new Deployer.DeploymentState();
dstate.bundles = new HashMap<>();
dstate.bundlesPerRegion = new HashMap<>();
dstate.filtersPerRegion = new HashMap<>();
dstate.state = new State();
MapUtils.addToMapSet(dstate.bundlesPerRegion, FeaturesService.ROOT_REGION, 0l);
dstate.bundles.put(0l, systemBundle.getBundle());
Collection<org.apache.karaf.features.Feature> features = new LinkedList<>();
for (Features repo : repositories) {
if (repo.isBlacklisted()) {
continue;
}
for (Feature f : repo.getFeature()) {
if (!f.isBlacklisted()) {
features.add(f);
}
}
}
dstate.partitionFeatures(features);
}
/**
* Get startup bundles with related start-level
* @return
*/
public Map<String, Integer> getStartupBundles() {
Map<String, Integer> startup = new HashMap<>();
for (Map.Entry<String, Bundle> bundle : bundles.entrySet()) {
int level = bundle.getValue().adapt(BundleStartLevel.class).getStartLevel();
if (level <= 0) {
level = defaultStartLevel;
}
startup.put(bundle.getKey(), level);
}
return startup;
}
public Deployer.DeploymentState getDeploymentState() {
return dstate;
}
@Override
public void saveState(State state) {
dstate.state.replace(state);
}
@Override
public void persistResolveRequest(Deployer.DeploymentRequest request) {
}
@Override
public void installConfigs(org.apache.karaf.features.Feature feature) throws IOException {
assertNotBlacklisted(feature);
// Install
Downloader downloader = manager.createDownloader();
for (Config config : ((Feature) feature).getConfig()) {
Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
if (Files.exists(configFile) && !config.isAppend()) {
LOGGER.info(" not changing existing config file: {}", homeDirectory.relativize(configFile));
continue;
}
if (config.isExternal()) {
downloader.download(config.getValue().trim(), provider -> {
Path input = provider.getFile().toPath();
byte[] data = Files.readAllBytes(input);
if (config.isAppend()) {
LOGGER.info(" appending to config file: {}", homeDirectory.relativize(configFile));
Files.write(configFile, data, StandardOpenOption.APPEND);
} else {
LOGGER.info(" adding config file: {}", homeDirectory.relativize(configFile));
Files.write(configFile, data);
}
});
} else {
byte[] data = config.getValue().getBytes();
if (config.isAppend()) {
LOGGER.info(" appending to config file: {}", homeDirectory.relativize(configFile));
Files.write(configFile, data, StandardOpenOption.APPEND);
} else {
LOGGER.info(" adding config file: {}", homeDirectory.relativize(configFile));
Files.write(configFile, data);
}
}
}
for (final ConfigFile configFile : ((Feature) feature).getConfigfile()) {
String path = configFile.getFinalname();
if (path.startsWith("/")) {
path = path.substring(1);
}
path = substFinalName(path);
final Path output = homeDirectory.resolve(path);
final String finalPath = path;
if (configFile.isOverride() || !Files.exists(output)) {
downloader.download(configFile.getLocation(), provider -> {
Path input = provider.getFile().toPath();
if (configFile.isOverride()) {
LOGGER.info(" overwriting config file: {}", finalPath);
} else {
LOGGER.info(" adding config file: {}", finalPath);
}
Files.copy(input, output, StandardCopyOption.REPLACE_EXISTING);
});
}
}
}
@Override
public void deleteConfigs(org.apache.karaf.features.Feature feature) throws IOException, InvalidSyntaxException {
// nothing to do
}
@Override
public void installLibraries(org.apache.karaf.features.Feature feature) throws IOException {
assertNotBlacklisted(feature);
Downloader downloader = manager.createDownloader();
List<String> libraries = new ArrayList<>();
for (Library library : ((Feature) feature).getLibraries()) {
String lib = library.getLocation() +
";type:=" + library.getType() +
";export:=" + library.isExport() +
";delegate:=" + library.isDelegate();
libraries.add(lib);
}
if (!libraries.isEmpty()) {
Path configPropertiesPath = etcDirectory.resolve("config.properties");
Properties configProperties = new Properties(configPropertiesPath.toFile());
builder.downloadLibraries(downloader, configProperties, libraries, " ");
}
try {
downloader.await();
} catch (Exception e) {
throw new IOException("Error downloading configuration files", e);
}
}
private void assertNotBlacklisted(org.apache.karaf.features.Feature feature) {
if (feature.isBlacklisted()) {
if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
throw new RuntimeException("Feature " + feature.getId() + " is blacklisted");
}
}
}
@Override
public void callListeners(DeploymentEvent deployEvent) {
}
@Override
public void callListeners(FeatureEvent featureEvent) {
}
@Override
public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
// Check blacklist
if (processor.isBundleBlacklisted(uri)) {
if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
throw new RuntimeException("Bundle " + uri + " is blacklisted");
}
}
// Install
LOGGER.info(" adding maven artifact: " + uri);
try {
String regUri;
String path;
if (uri.startsWith("mvn:")) {
regUri = uri;
path = Parser.pathFromMaven(uri);
} else {
uri = uri.replaceAll("[^0-9a-zA-Z.\\-_]+", "_");
if (uri.length() > 256) {
//to avoid the File name too long exception
uri = uri.substring(0, 255);
}
path = "generated/" + uri;
regUri = "file:" + path;
}
final Path bundleSystemFile = systemDirectory.resolve(path);
Files.createDirectories(bundleSystemFile.getParent());
Files.copy(is, bundleSystemFile, StandardCopyOption.REPLACE_EXISTING);
Hashtable<String, String> headers = new Hashtable<>();
try (JarFile jar = new JarFile(bundleSystemFile.toFile())) {
Attributes attributes = jar.getManifest().getMainAttributes();
for (Map.Entry<Object, Object> attr : attributes.entrySet()) {
headers.put(attr.getKey().toString(), attr.getValue().toString());
}
}
BundleRevision revision = new FakeBundleRevision(headers, uri, nextBundleId.incrementAndGet());
Bundle bundle = revision.getBundle();
MapUtils.addToMapSet(dstate.bundlesPerRegion, region, bundle.getBundleId());
dstate.bundles.put(bundle.getBundleId(), bundle);
bundles.put(regUri, bundle);
return bundle;
} catch (IOException e) {
throw new BundleException("Unable to install bundle", e);
}
}
@Override
public void setBundleStartLevel(Bundle bundle, int startLevel) {
bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
}
@Override
public void bundleBlacklisted(BundleInfo bundleInfo) {
LOGGER.info(" skipping blacklisted bundle: {}", bundleInfo.getLocation());
}
private String substFinalName(String finalname) {
final String markerVarBeg = "${";
final String markerVarEnd = "}";
boolean startsWithVariable = finalname.startsWith(markerVarBeg) && finalname.contains(markerVarEnd);
if (startsWithVariable) {
String marker = finalname.substring(markerVarBeg.length(), finalname.indexOf(markerVarEnd));
switch (marker) {
case "karaf.base":
return this.homeDirectory + finalname.substring(finalname.indexOf(markerVarEnd)+markerVarEnd.length());
case "karaf.etc":
return this.etcDirectory + finalname.substring(finalname.indexOf(markerVarEnd)+markerVarEnd.length());
default:
break;
}
}
return finalname;
}
}