blob: 8ed5ff813a82d49b27095223de8cac1b73037225 [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.kar.internal;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import org.apache.karaf.features.*;
import org.apache.karaf.kar.KarService;
import org.apache.karaf.util.StreamUtils;
import org.apache.karaf.util.maven.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the KAR service.
*/
public class KarServiceImpl implements KarService {
private static final String FEATURE_CONFIG_FILE = "features.cfg";
private static final Logger LOGGER = LoggerFactory.getLogger(KarServiceImpl.class);
private File storage;
private File base;
private FeaturesService featuresService;
private boolean noAutoRefreshBundles;
private boolean noAutoStartBundles;
private List<Kar> unsatisfiedKars;
private AtomicBoolean busy;
private DelayedDeployerThread delayedDeployerThread;
public KarServiceImpl(String karafBase, String karStorage, FeaturesService featuresService) {
this.base = new File(karafBase);
this.storage = new File(karStorage);
this.featuresService = featuresService;
this.storage.mkdirs();
if (!storage.isDirectory()) {
throw new IllegalStateException("KAR storage " + storage + " is not a directory");
}
unsatisfiedKars = Collections.synchronizedList(new ArrayList<Kar>());
busy = new AtomicBoolean();
}
@Override
public void install(URI karUri) throws Exception {
install(karUri, false);
}
@Override
public void install(URI karUri, boolean noAutoStartBundles) throws Exception {
String karName = new Kar(karUri).getKarName();
LOGGER.debug("Installing KAR {} from {}", karName, karUri);
File karDir = new File(storage, karName);
install(karUri, karDir, base, noAutoStartBundles);
}
@Override
public void install(URI karUri, File repoDir, File resourceDir) throws Exception {
install(karUri, repoDir, resourceDir, false);
}
@Override
public void install(URI karUri, File repoDir, File resourceDir, boolean noAutoStartBundles) throws Exception {
busy.set(true);
try {
Kar kar = new Kar(karUri);
kar.extract(repoDir, resourceDir);
writeToFile(kar.getFeatureRepos(), new File(repoDir, FEATURE_CONFIG_FILE));
for (URI uri : kar.getFeatureRepos()) {
addToFeaturesRepositories(uri);
}
if (kar.isShouldInstallFeatures()) {
List<URI> featureRepos = kar.getFeatureRepos();
Dependency missingDependency = findMissingDependency(featureRepos);
if (missingDependency == null) {
installFeatures(featureRepos, noAutoStartBundles);
}
else {
LOGGER.warn("Feature dependency {} is not available. Kar deployment postponed to see if it is about to be deployed",missingDependency);
unsatisfiedKars.add(kar);
if (delayedDeployerThread == null) {
delayedDeployerThread = new DelayedDeployerThread(noAutoStartBundles);
delayedDeployerThread.start();
}
}
}
if (!unsatisfiedKars.isEmpty()) {
for (Iterator<Kar> iterator = unsatisfiedKars.iterator(); iterator.hasNext();) {
Kar delayedKar = iterator.next();
if(findMissingDependency(delayedKar.getFeatureRepos())==null) {
LOGGER.info("Dependencies of kar {} are now satisfied. Installing",delayedKar.getKarName());
iterator.remove();
installFeatures(delayedKar.getFeatureRepos(), noAutoStartBundles);
}
}
}
if (unsatisfiedKars.isEmpty()) {
if (delayedDeployerThread != null) {
delayedDeployerThread.cancel();
}
delayedDeployerThread = null;
}
} finally {
busy.set(false);
}
}
/**
* checks if all required features are available
* @param featureRepos the repositories within the kar
* @return <code>null</code> if the contained features have no unresolvable dependencies. Otherwise the first missing dependency
* @throws Exception
*/
private Dependency findMissingDependency(List<URI> featureRepos)
throws Exception {
for (URI uri : featureRepos) {
Feature[] includedFeatures = featuresService.getRepository(uri).getFeatures();
for (Feature includedFeature : includedFeatures) {
List<Dependency> dependencies = includedFeature.getDependencies();
for (Dependency dependency : dependencies) {
Feature feature = featuresService.getFeature(dependency.getName(), dependency.getVersion());
if(feature==null)
{
return dependency;
}
}
}
}
return null;
}
private List<URI> readFromFile(File repoListFile) {
ArrayList<URI> uriList = new ArrayList<>();
FileReader fr = null;
try {
fr = new FileReader(repoListFile);
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
uriList.add(new URI(line));
}
} catch (Exception e) {
throw new RuntimeException("Error reading repo list from file " + repoListFile.getAbsolutePath(), e);
} finally {
try {
fr.close();
} catch (IOException e) {
LOGGER.warn("Error closing reader for file " + repoListFile, e);
}
}
return uriList;
}
private void writeToFile(List<URI> featuresRepositoriesInKar, File repoListFile) {
FileOutputStream fos = null;
PrintStream ps = null;
try {
fos = new FileOutputStream(repoListFile);
ps = new PrintStream(fos);
for (URI uri : featuresRepositoriesInKar) {
ps.println(uri);
}
ps.close();
fos.close();
} catch (Exception e) {
throw new RuntimeException("Error writing feature repo list to file " + repoListFile.getAbsolutePath(), e);
} finally {
closeStream(ps);
closeStream(fos);
}
}
private void deleteRecursively(File dir) {
if (dir.isDirectory()) {
File[] children = dir.listFiles();
for (File child : children) {
deleteRecursively(child);
}
}
dir.delete();
}
@Override
public void uninstall(String karName) throws Exception {
File karDir = new File(storage, karName);
if (!karDir.exists()) {
throw new IllegalArgumentException("The KAR " + karName + " is not installed");
}
List<URI> featuresRepositories = readFromFile(new File(karDir, FEATURE_CONFIG_FILE));
uninstallFeatures(featuresRepositories);
for (URI featuresRepository : featuresRepositories) {
featuresService.removeRepository(featuresRepository);
}
deleteRecursively(karDir);
}
@Override
public List<String> list() throws Exception {
List<String> kars = new ArrayList<>();
for (File kar : storage.listFiles()) {
if (kar.isDirectory()) {
kars.add(kar.getName());
}
}
return kars;
}
/**
* Add an URI to the list of features repositories.
*
* @param uri the URI to add.
* @throws Exception in case of add failure.
*/
private void addToFeaturesRepositories(URI uri) throws Exception {
try {
featuresService.removeRepository(uri);
featuresService.addRepository(uri, false);
LOGGER.info("Added feature repository '{}'", uri);
} catch (Exception e) {
LOGGER.warn("Unable to add repository '{}'", uri, e);
}
}
/**
* Install all features contained in the list of features XML.
*
* @param featuresRepositories the list of features XML.
*/
private void installFeatures(List<URI> featuresRepositories, boolean noAutoStartBundles) throws Exception {
for (Repository repository : featuresService.listRepositories()) {
for (URI karFeatureRepoUri : featuresRepositories) {
if (repository.getURI().equals(karFeatureRepoUri)) {
try {
for (Feature feature : repository.getFeatures()) {
if (feature.getInstall() == null || Feature.DEFAULT_INSTALL_MODE.equals(feature.getInstall())) {
EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
try {
LOGGER.debug("noAutoRefreshBundles is {}", isNoAutoRefreshBundles());
if (isNoAutoRefreshBundles()) {
options.add(FeaturesService.Option.NoAutoRefreshBundles);
}
LOGGER.debug("noAutoStartBundles is {} (default {})", noAutoStartBundles, this.noAutoStartBundles);
if (noAutoStartBundles || this.noAutoStartBundles) {
options.add(FeaturesService.Option.NoAutoStartBundles);
}
featuresService.installFeature(feature, options);
} catch (Exception e) {
LOGGER.warn("Unable to install Kar feature {}", feature.getName() + "/" + feature.getVersion(), e);
}
} else {
LOGGER.warn("Feature " + feature.getName() + "/" + feature.getVersion() + " has install flag set to \"manual\", so it's not automatically installed");
}
}
} catch (Exception e) {
LOGGER.warn("Can't get features for KAR {}", karFeatureRepoUri, e);
}
}
}
}
}
@Override
public void create(String repoName, List<String> features, PrintStream console) {
FileOutputStream fos = null;
JarOutputStream jos = null;
try {
Repository repo = featuresService.getRepository(repoName);
if (repo == null) {
throw new RuntimeException("Could not find a repository with name " + repoName);
}
String karPath = storage + File.separator + repoName + ".kar";
File karFile = new File(karPath);
karFile.getParentFile().mkdirs();
fos = new FileOutputStream(karFile);
Manifest manifest = createNonAutoStartManifest(repo.getURI());
jos = new JarOutputStream(new BufferedOutputStream(fos, 100000), manifest);
Map<URI, Integer> locationMap = new HashMap<>();
copyResourceToJar(jos, repo.getURI(), locationMap);
Map<String, Feature> featureMap = new HashMap<>();
for (Feature feature : repo.getFeatures()) {
featureMap.put(feature.getName(), feature);
}
Set<Feature> featuresToCopy = getFeatures(featureMap, features, 1);
for (Feature feature : featuresToCopy) {
if (console != null)
console.println("Adding feature " + feature.getName());
copyFeatureToJar(jos, feature, locationMap);
}
if (console != null)
console.println("Kar file created : " + karPath);
} catch (Exception e) {
throw new RuntimeException("Error creating kar: " + e.getMessage(), e);
} finally {
closeStream(jos);
closeStream(fos);
}
}
private Set<Feature> getFeatures(Map<String, Feature> featureMap, List<String> features, int depth) {
Set<Feature> featureSet = new HashSet<>();
if (depth > 5) {
// Break after some recursions to avoid endless loops
return featureSet;
}
if (features == null) {
featureSet.addAll(featureMap.values());
return featureSet;
}
for (String featureName : features) {
Feature feature = featureMap.get(featureName);
if (feature == null) {
System.out.println("Feature " + featureName + " not found in repository.");
//throw new RuntimeException();
} else {
featureSet.add(feature);
List<Dependency> deps = feature.getDependencies();
List<String> depNames = new ArrayList<>();
for (Dependency dependency : deps) {
depNames.add(dependency.getName());
}
featureSet.addAll(getFeatures(featureMap, depNames, depth ++));
}
}
return featureSet;
}
private Manifest createNonAutoStartManifest(URI repoUri) throws IOException {
String manifestSt = "Manifest-Version: 1.0\n" +
Kar.MANIFEST_ATTR_KARAF_FEATURE_START +": false\n" +
Kar.MANIFEST_ATTR_KARAF_FEATURE_REPOS + ": " + repoUri.toString() + "\n";
InputStream manifestIs = new ByteArrayInputStream(manifestSt.getBytes("UTF-8"));
Manifest manifest = new Manifest(manifestIs);
return manifest;
}
private void closeStream(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
LOGGER.warn("Error closing stream", e);
}
}
}
private void copyFeatureToJar(JarOutputStream jos, Feature feature, Map<URI, Integer> locationMap)
throws URISyntaxException {
// add bundles
for (BundleInfo bundleInfo : feature.getBundles()) {
URI location = new URI(bundleInfo.getLocation().trim());
copyResourceToJar(jos, location, locationMap);
}
// add config files
for (ConfigFileInfo configFileInfo : feature.getConfigurationFiles()) {
URI location = new URI(configFileInfo.getLocation().trim());
copyResourceToJar(jos, location, locationMap);
}
// add bundles and config files in conditionals
for (Conditional conditional : feature.getConditional()) {
for (BundleInfo bundleInfo : conditional.getBundles()) {
URI location = new URI(bundleInfo.getLocation().trim());
copyResourceToJar(jos, location, locationMap);
}
for (ConfigFileInfo configFileInfo : conditional.getConfigurationFiles()) {
URI location = new URI(configFileInfo.getLocation().trim());
copyResourceToJar(jos, location, locationMap);
}
}
}
private void copyResourceToJar(JarOutputStream jos, URI location, Map<URI, Integer> locationMap) {
if (locationMap.containsKey(location)) {
return;
}
try {
String noPrefixLocation = location.toString().substring(location.toString().lastIndexOf(":") + 1);
Parser parser = new Parser(noPrefixLocation);
String path = "repository/" + parser.getArtifactPath();
jos.putNextEntry(new JarEntry(path));
try (
InputStream is = location.toURL().openStream()
) {
StreamUtils.copy(is, jos);
}
locationMap.put(location, 1);
} catch (Exception e) {
LOGGER.error("Error adding " + location, e);
}
}
/**
* Uninstall all features contained in the list of features XML.
*
* @param featuresRepositories the list of features XML.
*/
private void uninstallFeatures(List<URI> featuresRepositories) throws Exception {
for (Repository repository : featuresService.listRepositories()) {
for (URI karFeatureRepoUri : featuresRepositories) {
if (repository.getURI().equals(karFeatureRepoUri)) {
try {
for (Feature feature : repository.getFeatures()) {
if (feature.getInstall() == null || Feature.DEFAULT_INSTALL_MODE.equals(feature.getInstall())) {
try {
featuresService.uninstallFeature(feature.getName(), feature.getVersion());
} catch (Exception e) {
LOGGER.warn("Unable to uninstall Kar feature {}", feature.getName() + "/" + feature.getVersion(), e);
}
}
}
} catch (Exception e) {
LOGGER.warn("Can't get features for KAR {}", karFeatureRepoUri, e);
}
}
}
}
}
public boolean isNoAutoRefreshBundles() {
return noAutoRefreshBundles;
}
public void setNoAutoRefreshBundles(boolean noAutoRefreshBundles) {
this.noAutoRefreshBundles = noAutoRefreshBundles;
}
public boolean isNoAutoStartBundles() {
return noAutoStartBundles;
}
public void setNoAutoStartBundles(boolean noAutoStartBundles) {
this.noAutoStartBundles = noAutoStartBundles;
}
private class DelayedDeployerThread extends Thread {
private boolean noAutoStartBundles;
private AtomicBoolean cancel;
public DelayedDeployerThread(boolean noAutoStartBundles) {
super("Delayed kar deployment");
cancel = new AtomicBoolean();
this.noAutoStartBundles = noAutoStartBundles;
}
public void cancel() {
cancel.set(true);
}
@Override
public void run() {
try {
while(busy.get() && !cancel.get()) {
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
}
} catch (InterruptedException e) {
// nothing to do
}
if (!cancel.get()) {
installDelayedKars();
}
}
private void installDelayedKars() {
for (Iterator<Kar> iterator = unsatisfiedKars.iterator(); iterator.hasNext();) {
Kar kar = iterator.next();
iterator.remove();
try {
installFeatures(kar.getFeatureRepos(), noAutoStartBundles);
} catch (Exception e) {
LOGGER.error("Delayed deployment of kar "+kar.getKarName()+" failed",e);
}
}
}
}
}