You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/04/10 16:15:55 UTC

[33/59] [abbrv] [KARAF-2852] Merge kar/core and kar/command

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java b/kar/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
new file mode 100644
index 0000000..506bdb3
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.command;
+
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.kar.command.completers.KarCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "kar", name = "uninstall", description = "Uninstall a KAR file.")
+@Service
+public class UninstallKarCommand implements Action {
+
+    @Argument(index = 0, name = "name", description = "The name of the KAR file to uninstall.", required = true, multiValued = false)
+    @Completion(KarCompleter.class)
+    private String name;
+
+    @Reference
+    private KarService karService;
+
+    @Override
+    public Object execute() throws Exception {
+        karService.uninstall(name);
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java b/kar/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
new file mode 100644
index 0000000..539cfaf
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.command.completers;
+
+import java.util.List;
+
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+/**
+ * Completer on all installed KAR files.
+ */
+@Service
+public class KarCompleter implements Completer {
+
+    @Reference
+    private KarService karService;
+    
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (String karName : karService.list()) {
+                delegate.getStrings().add(karName);
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java b/kar/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java
new file mode 100644
index 0000000..aca1f53
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java
@@ -0,0 +1,87 @@
+/*
+ * 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.File;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Simple helper to determine if a file is a feature repo
+ */
+class FeatureDetector {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FeatureDetector.class);
+    
+    private DocumentBuilderFactory dbf;
+
+    FeatureDetector() {
+        dbf = DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+    }
+    /**
+     * Check if a file is a features XML.
+     *
+     * @param artifact the file to check.
+     * @return true if the artifact is a features XML, false else.
+     */
+    boolean isFeaturesRepository(File artifact) {
+        try {
+            if (artifact.isFile() && artifact.getName().endsWith(".xml")) {
+                Document doc = parse(artifact);
+                String name = doc.getDocumentElement().getLocalName();
+                String uri  = doc.getDocumentElement().getNamespaceURI();
+                if ("features".equals(name) && (uri == null || "".equals(uri) || uri.startsWith("http://karaf.apache.org/xmlns/features/v"))) {
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.debug("File '{}' is not a features file.", artifact.getName(), e);
+        }
+        return false;
+    }
+    
+    /**
+     * Parse a features XML.
+     *
+     * @param artifact the features XML to parse.
+     * @return the parsed document.
+     * @throws Exception in case of parsing failure.
+     */
+    private Document parse(File artifact) throws Exception {
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        db.setErrorHandler(new ErrorHandler() {
+            public void warning(SAXParseException exception) throws SAXException {
+            }
+            public void error(SAXParseException exception) throws SAXException {
+            }
+            public void fatalError(SAXParseException exception) throws SAXException {
+                throw exception;
+            }
+        });
+        return db.parse(artifact);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/internal/Kar.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/Kar.java b/kar/src/main/java/org/apache/karaf/kar/internal/Kar.java
new file mode 100644
index 0000000..005cd17
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/internal/Kar.java
@@ -0,0 +1,203 @@
+/*
+ * 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.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Representation of a Karaf Kar archive
+ * 
+ * A Kar archive is a jar file with a special structure that can be used
+ * to deploy feature repositories, maven repo contents and resources for the
+ * karaf installation.
+ * 
+ * meta-inf/Manifest: 
+ *   Karaf-Feature-Start: (true|false) Controls if the features in the feature repos should be started on deploy
+ *   Karaf-Feature-Repos: (uri)* If present then only the given feature repo urls are added to karaf if it is not
+ *      present then the karaf file is scanned for repo files
+ *      
+ * repository/
+ *   Everything below this directory is treated as a maven repository. On deploy the contents
+ *   will be copied to a directory below data. This directory will then be added to the 
+ *   maven repos of pax url maven
+ *   
+ * resource/
+ *   Everything below this directory will be copied to the karaf base dir on deploy
+ * 
+ */
+public class Kar {
+
+    public static final Logger LOGGER = LoggerFactory.getLogger(KarServiceImpl.class);
+    public static final String MANIFEST_ATTR_KARAF_FEATURE_START = "Karaf-Feature-Start";
+    public static final String MANIFEST_ATTR_KARAF_FEATURE_REPOS = "Karaf-Feature-Repos";
+    private final URI karUri;
+    private boolean shouldInstallFeatures;
+    private List<URI> featureRepos;
+
+    public Kar(URI karUri) {
+        this.karUri = karUri;
+    }
+
+    /**
+     * Extract a kar from a given URI into a repository dir and resource dir
+     * and populate shouldInstallFeatures and featureRepos
+     *
+     * @param repoDir directory to write the repository contents of the kar to
+     * @param resourceDir directory to write the resource contents of the kar to
+     */
+    public void extract(File repoDir, File resourceDir) {
+        InputStream is = null;
+        JarInputStream zipIs = null;
+        FeatureDetector featureDetector = new FeatureDetector();
+        this.featureRepos = new ArrayList<URI>();
+        this.shouldInstallFeatures = true;
+
+        try {
+            is = karUri.toURL().openStream();
+            repoDir.mkdirs();
+
+            if (!repoDir.isDirectory()) {
+                throw new RuntimeException("The KAR file " + karUri + " is already installed");
+            }
+
+            LOGGER.debug("Uncompress the KAR file {} into directory {}", karUri, repoDir);
+            zipIs = new JarInputStream(is);
+            boolean scanForRepos = true;
+
+            Manifest manifest = zipIs.getManifest();
+            if (manifest != null) {
+                Attributes attr = manifest.getMainAttributes();
+                String featureStartSt = (String)attr
+                    .get(new Attributes.Name(MANIFEST_ATTR_KARAF_FEATURE_START));
+                if ("false".equals(featureStartSt)) {
+                    shouldInstallFeatures = false;
+                }
+                String featureReposAttr = (String)attr
+                    .get(new Attributes.Name(MANIFEST_ATTR_KARAF_FEATURE_REPOS));
+                if (featureReposAttr != null) {
+                    featureRepos.add(new URI(featureReposAttr));
+                    scanForRepos = false;
+                }
+            }
+
+            ZipEntry entry = zipIs.getNextEntry();
+            while (entry != null) {
+                if (entry.getName().startsWith("repository")) {
+                    String path = entry.getName().substring("repository/".length());
+                    File destFile = new File(repoDir, path);
+                    extract(zipIs, entry, destFile);
+                    if (scanForRepos && featureDetector.isFeaturesRepository(destFile)) {
+                        featureRepos.add(destFile.toURI());
+                    }
+                }
+
+                if (entry.getName().startsWith("resource")) {
+                    String path = entry.getName().substring("resource/".length());
+                    File destFile = new File(resourceDir, path);
+                    extract(zipIs, entry, destFile);
+                }
+                entry = zipIs.getNextEntry();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error extracting kar file " + karUri + " into dir " + repoDir + ": " + e.getMessage(), e);
+        } finally {
+            closeStream(zipIs);
+            closeStream(is);
+        }
+    }
+
+    /**
+     * Extract an entry from a KAR file
+     * 
+     * @param is
+     * @param zipEntry
+     * @param dest
+     * @return
+     * @throws Exception
+     */
+    private static File extract(InputStream is, ZipEntry zipEntry, File dest) throws Exception {
+        if (zipEntry.isDirectory()) {
+            LOGGER.debug("Creating directory {}", dest.getName());
+            dest.mkdirs();
+        } else {
+            dest.getParentFile().mkdirs();
+            FileOutputStream out = new FileOutputStream(dest);
+            copyStream(is, out);
+            out.close();
+        }
+        return dest;
+    }
+
+    private static void closeStream(InputStream is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException e) {
+                LOGGER.warn("Error closing stream", e);
+            }
+        }
+    }
+
+    static long copyStream(InputStream input, OutputStream output) throws IOException {
+        byte[] buffer = new byte[10000];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    public String getKarName() {
+        try {
+            String karName = new File(karUri.toURL().getFile()).getName();
+            karName = karName.substring(0, karName.lastIndexOf("."));
+            return karName;
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Invalid kar URI " + karUri, e);
+        }
+    }
+    
+    public URI getKarUri() {
+        return karUri;
+    }
+
+    public boolean isShouldInstallFeatures() {
+        return shouldInstallFeatures;
+    }
+
+    public List<URI> getFeatureRepos() {
+        return featureRepos;
+    } 
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java b/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java
new file mode 100644
index 0000000..0f86466
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java
@@ -0,0 +1,382 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.kar.KarService;
+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;
+
+    public KarServiceImpl(String karafBase, FeaturesService featuresService) {
+        this.base = new File(karafBase);
+        this.storage = new File(this.base, "data" + File.separator + "kar");
+        this.featuresService = featuresService;
+        this.storage.mkdirs();
+        if (!storage.isDirectory()) {
+            throw new IllegalStateException("KAR storage " + storage + " is not a directory");
+        }
+    }
+    
+    @Override
+    public void install(URI karUri) 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);
+    }
+    
+    @Override
+    public void install(URI karUri, File repoDir, File resourceDir) throws Exception {
+        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()) {
+            installFeatures(kar.getFeatureRepos());
+        }
+
+    }
+
+
+    private List<URI> readFromFile(File repoListFile) {
+        ArrayList<URI> uriList = new ArrayList<URI>();
+        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<String>();
+        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);
+            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) throws Exception {
+        for (Repository repository : featuresService.listRepositories()) {
+            for (URI karFeatureRepoUri : featuresRepositories) {
+                if (repository.getURI().equals(karFeatureRepoUri)) {
+                    try {
+                        for (Feature feature : repository.getFeatures()) {
+                            try {
+                                LOGGER.debug("noAutoRefreshBundles is " + isNoAutoRefreshBundles());
+                                if (isNoAutoRefreshBundles()) {
+                                    featuresService.installFeature(feature, EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
+                                } else {
+                                    featuresService.installFeature(feature, EnumSet.noneOf(FeaturesService.Option.class));
+                                }
+                            } catch (Exception e) {
+                                LOGGER.warn("Unable to install Kar feature {}", feature.getName() + "/" + feature.getVersion(), e);
+                            }
+                        }
+                    } 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<URI, Integer>();
+            copyResourceToJar(jos, repo.getURI(), locationMap);
+        
+            Map<String, Feature> featureMap = new HashMap<String, Feature>();
+            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<Feature>();
+        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<String>();
+                for (Dependency dependency : deps) {
+                    depNames.add(dependency.getName());
+                }
+                featureSet.addAll(getFeatures(featureMap, depNames, depth ++));
+            }
+        }
+        return featureSet;
+    }
+
+    private Manifest createNonAutoStartManifest(URI repoUri) throws UnsupportedEncodingException, 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 {
+        for (BundleInfo bundleInfo : feature.getBundles()) {
+            URI location = new URI(bundleInfo.getLocation());
+            copyResourceToJar(jos, location, locationMap);
+        }
+        for (ConfigFileInfo configFileInfo : feature.getConfigurationFiles()) {
+            URI location = new URI(configFileInfo.getLocation());
+            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);
+            InputStream is = location.toURL().openStream();
+            String path = "repository/" + parser.getArtifactPath();
+            jos.putNextEntry(new JarEntry(path));
+            Kar.copyStream(is, jos);
+            is.close();
+            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()) {
+                            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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/internal/KarsMBeanImpl.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/KarsMBeanImpl.java b/kar/src/main/java/org/apache/karaf/kar/internal/KarsMBeanImpl.java
new file mode 100644
index 0000000..b274e21
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/internal/KarsMBeanImpl.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.apache.karaf.kar.KarService;
+import org.apache.karaf.kar.KarsMBean;
+
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import java.net.URI;
+import java.util.List;
+
+public class KarsMBeanImpl extends StandardMBean implements KarsMBean {
+
+    private KarService karService;
+
+    public KarsMBeanImpl() throws NotCompliantMBeanException {
+        super(KarsMBean.class);
+    }
+
+    public List<String> getKars() throws MBeanException {
+        try {
+            return karService.list();
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void create(String repoName, List<String> features) {
+        karService.create(repoName, features, null);
+    }
+
+    public void install(String url) throws MBeanException {
+        try {
+            karService.install(new URI(url));
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void uninstall(String name) throws MBeanException {
+        try {
+            karService.uninstall(name);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public KarService getKarService() {
+        return karService;
+    }
+
+    public void setKarService(KarService karService) {
+        this.karService = karService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/java/org/apache/karaf/kar/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/osgi/Activator.java b/kar/src/main/java/org/apache/karaf/kar/internal/osgi/Activator.java
new file mode 100644
index 0000000..05a1b59
--- /dev/null
+++ b/kar/src/main/java/org/apache/karaf/kar/internal/osgi/Activator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.osgi;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.kar.internal.KarServiceImpl;
+import org.apache.karaf.kar.internal.KarsMBeanImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.osgi.service.cm.ManagedService;
+
+public class Activator extends BaseActivator implements ManagedService {
+
+    @Override
+    protected void doOpen() throws Exception {
+        manage("org.apache.karaf.kar");
+        trackService(FeaturesService.class);
+    }
+
+    protected void doStart() throws Exception {
+        FeaturesService featuresService = getTrackedService(FeaturesService.class);
+        if (featuresService == null) {
+            return;
+        }
+
+        boolean noAutoRefreshBundles = getBoolean("noAutoRefreshBundles", false);
+
+        KarServiceImpl karService = new KarServiceImpl(
+                System.getProperty("karaf.base"),
+                featuresService
+        );
+        karService.setNoAutoRefreshBundles(noAutoRefreshBundles);
+        register(KarService.class, karService);
+
+        KarsMBeanImpl mbean = new KarsMBeanImpl();
+        mbean.setKarService(karService);
+        registerMBean(mbean, "type=kar");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/f13d140c/kar/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/kar/src/main/resources/OSGI-INF/bundle.info b/kar/src/main/resources/OSGI-INF/bundle.info
new file mode 100644
index 0000000..7056453
--- /dev/null
+++ b/kar/src/main/resources/OSGI-INF/bundle.info
@@ -0,0 +1,19 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+[mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+This bundle is the core implementation of the Karaf kar support.
+
+Karaf Archives (KAR) is an artifact (zip file) shipping a features XML and the associated bundles or configuration
+files.
+
+h1. See also
+
+KAR - section of the Karaf User Guide